正点原子STM32(基于HAL库)0

目录

开发环境搭建与使用

对应第三章第四章节

常用开发工具简介

我们开发STM32 需要用到一些开发工具,如:IDE、仿真器、串口调试助手等。常见的工具如表3.1.1 所示:

在这里插入图片描述

MDK 安装

注意:MDK 是一款付费集成开发环境,如果大家要商用,请联系Keil 公司购买,我们这里仅用于教学使用。

MDK5 的安装分为两步:

  • 1,安装MDK5;
  • 2,安装器件支持包。

MDK 软件下载地址:https://www.keil.com/download/product,目前最新版本是MDK5.36。器件支持包下载地址:https://www.keil.com/dd2/pack,STM32F1 支持包最新版本是2.3.0。
MDK5.36 和2.3.0 的STM32F1 器件支持包我们都已经放在光盘A 盘了,具体路径为:A 盘→ 6,软件资料→1,软件→MDK5,如图3.2.1 所示:

在这里插入图片描述

MDK5 的安装比较简单,具体安装步骤请参考图3.2.1 的安装过程.txt 进行安装即可,需要提醒一下大家,在选择安装路径的时候,强烈建议大家将Pack 的路径和Core 的路径放在一个位置,比如我们安装在D 盘(都安装在:D:\MDK5.36 路径下),如图3.2.2 所示:

在这里插入图片描述

安装完成后,在我们电脑桌面会显示MDK5 图标,如图3.2.3 所示;

在这里插入图片描述

仿真器驱动安装

STM32 可以通过DAP、ST LINK、JLINK 等仿真调试器进行程序下载和仿真,我们默认推荐使用:DAP 仿真器(CMSIS-DAP Debugger),DAP 仿真器在MDK 下是免驱动的(无需安装驱动),即插即用,非常方便。

正点原子提供两种规格的DAP 仿真器:普速版本DAP(ATK-DAP)和高速版本(ATK-HSDAP),这两个版本DAP 使用完全一样,只是高速版本速度更快,大家根据需要选择即可。

如果你用的是STLINK 仿真器,大家可以参考A 盘→ 6,软件资料→1,软件→ST LINK 驱动及教程→ST LINK 调试补充教程.pdf,进行驱动安装。

CH340 USB 虚拟串口驱动安装

安装CH340 USB 虚拟串口驱动,以便我们使用电脑通过USB 和STM32 进行串口通信。开发板使用的USB 虚拟串口芯片是CH340C,其驱动我们已经放在开发板A 盘→6,软件资料→1,软件→ CH340 驱动(USB 串口驱动)_XP_WIN7 共用文件夹里面,如图3.4.1 所示:

在这里插入图片描述

双击SETUP.EXE 进行安装,安装完成后,如图3.4.2 所示:

在这里插入图片描述

在驱动安装成功之后,将开发板的USB_UART 接口通过USB 连接到电脑,此时电脑就会自动给其安装驱动了。在安装完成之后,可以在电脑的设备管理器里面找到USB 串口(如果找不到,则重启下电脑),如图3.4.3 所示:

在这里插入图片描述

在图3.4.3 中可以看到,我们的USB 虚拟串口被识别为COM3,这里需要注意的是:不同电脑可能不一样,你的可能是COM4、COM5 等,但是USB-SERIAL CH340,这个是一样的。如果没找到USB 串口,则有可能是你安装有误,或者系统不兼容。

使用 MDK5 编译例(Code、RO-Data、RW-Data、ZI-Data)

我们在编写完代码以后,需要对代码进行编译,编译成功以后,才能下载到开发板进行验证、调试等操作。战舰开发板标准例程源码路径:A 盘→4,程序源码,如图4.1.1 所示

在这里插入图片描述

这4 个压缩包说明如表4.1.1:

在这里插入图片描述

本书主要针对“标准例程-HAL 库版本”源码进行讲解,所以,这里我们先解压该压缩包,得到HAL 库版本的标准例程源码如图4.1.2 所示:

在这里插入图片描述

战舰V4 总共听过54 个实验的标准例程,部分实验有多个例程,比如:通用定时器实验、高级定时器实验、ADC 实验等,所以我们的总例程数达到了69 个。这里我们没有把实验0 统计进来,因为实验0 主要是新建工程等MDK 使用基础教学例程。

秉着简单易懂的原则,我们选择:实验1,跑马灯实验作为STM32 入门体验,例程目录如图4.1.3 所示:

在这里插入图片描述

工程目录下有5 个文件夹,我们将在后续“新建HAL 库版本MDK 工程”这一章给大家详细介绍其作用(其实看文件夹名字的字面意思基本就知道是做啥用的了),这里我们不多说。例程MDK 工程文件的路径为:Projects→MDK-ARM→atk_f103.uvprojx,如图4.1.4 所示:

在这里插入图片描述

注意:一定要先安装MDK5(详见第三章),否则无法打开该工程文件!
双击atk_f103.uvprojx 打开该工程文件,进入MDK IDE 界面,如图4.1.5 所示:

在这里插入图片描述

①是编译按钮,表示编译当前工程项目文件,如果之前已经编译过了,则只会编译有改动的文件。所以一般第一次会比较耗时间,后续因为只编译改动文件,从而大大缩短了编译时间。该按钮可以通过F7 快捷键进行操作。
②是重新编译当前工程所有文件按钮,工程代码较多时全部重新编译会耗费比较多的时间,建议少用。

按①处的按钮,编译当前项目,在编译完成后,可以看到如图4.1.6 所示的编译提示信息:

在这里插入图片描述

图中:

  • Code:表示代码大小,占用5442 字节,占用FLASH
  • RO-Data:表示只读数据所占的空间大小,一般是指const 修饰的数据大小,占用FLASH
  • RW-Data:表示有初值(非0)的可读写数据所占的空间大小,它同时占用FLASH(存放其初始值)和 RAM 空间
  • ZI-Data:表示初始化为 0 的可读写数据所占空间大小,它只占用RAM 空间

因此图4.1.6 的提示信息表示:代码总大小(Porgram Size)为:FLASH 占用5832 字节(Code + RO + RW),SRAM 占用1928 字节(RW + ZI)。

成功创建了Hex 文件(可执行文件,放在Output 目录下);编译0 错误,0 警告;编译耗时2 秒钟。编译完成以后,会生成Hex 可执行文件,默认输出在Output 文件夹下,如图4.1.7 所示:

在这里插入图片描述

Output 文件夹下除了.hex 文件还有很多其他文件(.axf、.htm、.dep、.lnp、.o、.d、.lst 等),这些文件是编译过程所产生的中间文件,我们将在后续的.map 文件分析给大家详细介绍这些文件的作用。

使用串口下载程序

STM32 常见的下载方式有三种:

在这里插入图片描述

经过以上对比,因此我们推荐使用串口下载(没有仿真器的情况下)和SWD 下载(有仿真器的情况下),强烈推荐大家购买一个仿真器(如ST LINK、CMSIS DAP 等),可以极大的方便学习和开发。

表中BOOT0 和BOOT1 是STM32 芯片上面的两个引脚,用于控制STM32 的启动方式,具体如表4.2.2 所示:

在这里插入图片描述

由于从系统存储器启动,是不运行用户程序的,所以不管用户程序怎么写,此模式下都可以通过仿真器下载代码。所以,该模式可常用于异常关闭JTAG/SWD 导致仿真器无法下载程序时的补救措施(在此模式下,下载一个不关闭JTAG/SWD 接口的程序即可救活)。

本节,我们将向大家介绍,如何利用串口给STM32F103(以下简称STM32)下载代码。
STM32 通过串口1 实现程序下载,战舰开发板通过自带的USB 转串口来实现串口下载。看起来像是USB 下载(只需一根USB 线,并不需要串口线)的,实际上,是通过USB 转成串口,然后再下载的。

下面,我们就一步步教大家如何在实验平台上利用USB 串口来下载代码。
首先确保开发板接线正确,并成功上电,默认的出厂设置如图4.2.1 所示:

在这里插入图片描述

a) ①处的USB_UART 通过USB 线连接电脑,实现USB 转串口,同时支持给开发板供电。
b) 确保电源灯②亮起(蓝色),如果不亮检查供电和电源开关③是否按下?
c) 确保P4 端子的RXD 和PA9(STM32 的TXD),TXD 和PA10(STM32 的RXD)通过跳线帽连接起来(标号④),这样我们就把CH340C 和STM32 的串口1 连接上了。
d) ⑤处的BOOT 设置为BOOT0(简称B0)和BOOT1(简称B1)都接GND(一键下载电路自动控制,待会介绍)。
由表4.2.1 可知,使用串口下载STM32 程序需要修改BOOT 的设置,四个步骤如下:
1,把B0 接V3.3
2,保持B1 接GND
3,按一下复位按键
4,使用上位机软件下载代码

通过这几个步骤,我们就可以通过串口下载代码了,下载完成之后,如果没有设置从0X08000000 开始运行,则代码不会立即运行,此时,你还需要把B0 接回GND,然后再按一次复位,才会开始运行你刚刚下载的代码。所以整个过程,你得跳动2 次跳线帽,还得按2 次复位,比较繁琐。而我们的一键下载电路,则利用串口的DTR 和RTS 信号,分别控制STM32 的复位和B0,配合上位机软件(flymcu,即mcuisp 的最新版本),设置:DTR 的低电平复位,

RTS 高电平进BootLoader,这样,B0 和STM32 的复位,完全可以由下载软件自动控制,从而实现一键下载。一键下载的原理图级别解释详见:战舰V4 硬件参考手册.pdf串口程序下载需要用到:flymcu 这个上位机软件。这个软件可以实现对STM32F103 和STM32F407 等系列芯片的串口编程(注意,该软件暂时不支持M7,所以STM32F7 和H7 不能使用该软件进行串口编程)。flymcu 软件在光盘路径:A 盘→ 6,软件资料→1,软件→STM32串口下载软件(FLYMCU),如图4.2.2 所示:

在这里插入图片描述

双击打开flymcu,进行如图4.2.3 所示的设置:

在这里插入图片描述

①搜索串口选择CH340 虚拟的串口(我的是COM3,不同电脑可能不同,需要根据实际情况选择对应的串口,详见3.4 节),然后设置波特率为460800,保证最大速度下载。
②选择4.1 节编译生成的hex 文件(在Output 文件夹)
③勾选校验和编程后执行两项,可以保证下载代码的正确性,并下载完后自动运行,省去按复位的麻烦。注意:千万不要勾选:使用RamIsp 和连续烧录模式!否则下载失败!
④选择DTR 的低电平复位,RTS 高电平进BootLoader(别选错!),以匹配一键下载电路,实现一键下载代码,省去设置BOOT0、按复位的麻烦。

设置好之后,我们就可以通过按开始编程(P)这个按钮,一键下载代码到STM32 上,下载成功后如图4.2.4 所示:

在这里插入图片描述

图4.2.4 中,我们圈出了flymcu 对一键下载电路的控制过程,其实就是控制DTR 和RTS电平的变化,控制BOOT0 和RESET,从而实现自动下载。

另外,下载成功后,会有“共写入xxxxKB,进度100%,耗时xxxx 毫秒”的提示,并且从0X80000000 处开始运行了。此时表示代码下载完成,并已经成功运行了,我们查看开发板就可以看到DS0(红灯)、DS1(绿灯)开始交替闪烁了,如图4.2.5 所示:

在这里插入图片描述

使用DAP 下载与调试程序

上一节我们使用串口给STM32 下载程序,但是串口下载并不能仿真调试代码,只能下载后观看运行结果,所以在调试代码bug 的时候,最好还是用仿真器进行下载调试,本节我们将给大家介绍如何使用仿真器给STM32 下载代码,并调试代码。

这里我们以DAP 仿真器为例给大家进行讲解,如果你用的是其他仿真器,基本上也是一样的用法,只是选择仿真器的时候,选择对应的型号即可。

DAP 与开发板连接如图4.3.1 所示:
a) DAP 通过USB 线连接电脑,且仿真器①处的蓝灯常亮。
b) DAP 通过20P 灰排线连接开发板。
c) 确保开发板已经正常供电(可经由图中的⑤⑥给开发板供电,但我们建议使用⑤处的电源插座+开发板适配的DC 直流电源,以避免部分需要用到大电流的例程运行不正常),供电后确保⑦处的开关是按下的状态,这里蓝色电源灯亮起表示开发板已经正常供电。
d) B0,B1 都接GND。

在这里插入图片描述

使用 DAP 下载程序

在4.1 节的跑马灯例程MDK IDE 界面下,点击按钮,打开Options for Target 选项卡,在Debug 栏选择仿真工具为use:CMSIS-DAP Debugger,如图4.3.1.1 所示:

在这里插入图片描述

①选择使用CMSIS-DAP Debugger 仿真器仿真调试代码。如果你使用的是其他仿真器,比如STLINK、JLINK 等,请在这里选择对应的仿真器型号。
②该选项选中后,只要点击仿真就会直接运行到main 函数,如果没选择这个选项,则会先执行startup_stm32f103xe.s 文件的Reset_Handler,再跳到main 函数。

然后我们点击Settings,设置DAP 的一些参数,如图4.3.1.2 所示:
在这里插入图片描述
①表示MDK 找到了ATK CMSIS-DAP 仿真器,如果这里显示为空,则表示没有仿真器被找到,请检查你的电脑是否接了仿真器?并安装了对应的驱动?
②设置接口方式,这里选择SW(比JTAG 省IO),通信速度设置为10Mhz(实际上大概只有4M 的速度,MDK 会自动匹配)。
③表示MDK 通过仿真器的SW 接口找到了目标芯片,ID 为:0x1BA01477。如果这里显示:No target connected,则表示没找到任何器件,请检查仿真器和开发板连接是否正常?开发板是否供电了?

其他部分使用默认设置,设置完成以后单击“确定”按钮,完成此部分设置,接下来我们还需要在Utilities 选项卡里面设置下载时的目标编程器,如图4.3.1.3 所示:

在这里插入图片描述

上图中,我们直接勾选Use Debug Driver,即和调试一样,选择DAP 来给目标器件的FLASH编程,然后点击Settings,进入FLASH 算法设置,设置如图4.3.1.4 所示:

在这里插入图片描述
这里MDK5 会根据我们新建工程时选择的目标器件,自动设置flash 算法。我们使用的是STM32F103ZET6,FLASH 容量为512K 字节,所以Programming Algorithm 里面默认会有512K型号的STM32F10x High-density Flash 算法。另外,如果这里没有flash 算法,大家可以点击Add按钮,自行添加即可。最后,选中Reset and Run 选项,以实现在编程后自动运行,其他默认设置即可。

在设置完之后,点击“确定”,然后再点击“OK”,回到IDE 界面,编译(可按F7 快捷键)一下工程,编译完成以后(0 错误,0 警告),我们按(快捷键:F8)这个按钮,就可以将代码通过仿真器下载到开发板上,在IDE 下方的Build Output 窗口会提示相关信息,如下图所示:

在这里插入图片描述

下载完后,就可以看到DS0 和DS1 交替闪烁了,说明代码下载成功。

使用DAP 仿真调试程序

在这里插入图片描述

  • ①Register:寄存器窗口,显示了Cortex M3 内核寄存器R0~ R15 的值,还显示了内部的线程模式(处理者模式、线程模式)及特权级别(用户级、特权级),并且还显示了当前程序的运行时间(Sec),该选项卡一般用于查看程序运行时间,或者比较高级的bug查找(涉及到分析R0~R14 数据是否异常了)。

  • ②Disassembly:反汇编窗口,将C 语言代码和汇编对比显示(指令存放地址,指令代码,指令,具体操作),方便从汇编级别查看程序运行状态,同样也属于比较高级别的bug查找。

  • ③代码窗口,在左侧有黄绿色三角形,黄色的三角形表示将要执行的代码,绿色的三角形表示当前光标所在代码(C 代码或当前汇编行代码对应的C 代码)。一般情况下,这两个三角形是同步的,只有在点击光标查看代码的时候,才可能不同步。

  • ④Call Stack + Locals:调用关系&局部变量窗口,通过该窗口可以查看函数调用关系,以及函数的局部变量,在仿真调试的时候,是非常有用的。

开始仿真的默认窗口,我们就给大家介绍这几个,实际上还有一些其他的窗口,比如Watch、Memory、外设寄存器等也是很常用的,可以根据实际使用选择调用合适的窗口来查看对应的数据。

图4.3.2.1 中,还有一个很重要的工具条:Debug 工具条,其内容和作用如图4.3.2.2 所示:

在这里插入图片描述

  • 复位:其功能等同于硬件上按复位按钮。相当于实现了一次硬复位。按下该按钮之后,代码会重新从头开始执行。
  • 执行到断点处:该按钮用来快速执行到断点处,有时候你并不需要观看每步是怎么执行的,而是想快速的执行到程序的某个地方看结果,这个按钮就可以实现这样的功能,前提是你在查看的地方设置了断点。
  • 停止运行:此按钮在程序一直执行的时候会变为有效,通过按该按钮,就可以使程序停止下来,进入到单步调试状态。
  • 执行进去:该按钮用来实现执行到某个函数里面去的功能,在没有函数的情况下,是等同于执行过去按钮的。
  • 执行过去:在碰到有函数的地方,通过该按钮就可以单步执行过这个函数,而不进入这个函数单步执行。
  • 执行出去:该按钮是在进入了函数单步调试的时候,有时候可能不必再执行该函数的剩余部分了,通过该按钮就可以一步执行完该函数的余部分,并跳出函数,回到函数被调用的位置。
  • 执行到光标处:该按钮可以迅速的使程序运行到光标处,其实是挺像执行到断点处按钮功能,但是两者是有区别的,断点可以有多个,但是光标所在处只有一个。
  • 反汇编窗口:通过该按钮,就可以查看汇编代码,这对分析程序很有用。
  • Call STACK 窗口:通过该按钮,显示调用关系&局部变量窗口,显示当前函数的调用关系和局部变量,方便查看,对分析程序非常有用。
  • 观察窗口:MDK5 提供2 个观察窗口(下拉选择),该按钮按下,会弹出一个显示变量的窗口,输入你所想要观察的变量/表达式,即可查看其值,是很常用的一个调试窗口。
  • 内存查看窗口:MDK5 提供4 个内存查看窗口(下拉选择),该按钮按下,会弹出一个内存查看窗口,可以在里面输入你要查看的内存地址,然后观察这一片内存的变化情况。是很常用的一个调试窗口
  • 串口打印窗口:MDK5 提供4 个串口打印窗口(下拉选择),该按钮按下,会弹出一个类似串口调试助手界面的窗口,用来显示从串口打印出来的内容
  • 系统分析窗口:该图标下面有6 个选项(下拉选择),我们一般用第一个,也就是逻辑分析窗口(Logic Analyzer),点击即可调出该窗口,通过SETUP 按钮新建一些IO 口,就可以观察这些IO 口的电平变化情况,以多种形式显示出来,比较直观。
  • 系统查看窗口:该按钮可以提供各种外设寄存器的查看窗口(通过下拉选择),选择对应外设,即可调出该外设的相关寄存器表,并显示这些寄存器的值,方便查看设置的是否正确。

Debug 工具条上的其他几个按钮用的比较少,我们这里就不介绍了。以上介绍的是比较常
用的,当然也不是每次都用得着这么多,具体看你程序调试的时候有没有必要观看这些东西,来决定要不要看。

我们在图4.3.2.1 的基础上:关闭反汇编窗口(Disassembly)、添加观察窗口1(Watch1)。然后调节一下窗口位置,然后将全局变量:g_fac_us (在delay.c 里面定义)加入Watch1 窗口(方法:双击Enter expression 添加/直接拖动变量t 到Watch1 窗口即可),如图4.3.2.3 所示:

在这里插入图片描述
此时可以看到Watch1 窗口的g_fac_us 的值提示:无法计算,我们把鼠标光标放在第32 行左侧的灰色区域,然后按下鼠标左键,即可放置一个断点(红色的实心点,也可以通过鼠标右键弹出菜单来加入),这样就在delay_init 函数处放置一个断点,然后点击:,执行到该断点处,然后再点击:,执行进入delay_init 函数,如图4.3.2.4 所示:
在这里插入图片描述
在这里插入图片描述
然后再单步执行过去这一行,就可以在Watch1 窗口中看到g_fac_us 的值变成9 了,如图
4.3.2.6 所示:
在这里插入图片描述
由此可知,某些全局变量,我们在程序还没运行到其所在文件的时候,MDK 仿真时可能不会显示其值(如提示:cannot evaluate),当我们运行到其所在文件,并实际使用到的时候,此时就会显示其值出来了!

然后,我们再回到main.c,在第一个LED0(0)处放置一个断点,运行到断点处,如图4.3.2.7所示:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

此时,我们可以从Call Stack + Locals 窗口看到函数的调用关系,其原则是:从下往上看,即下一个函数调用了上一个函数,因此,其关系为:main 函数调用了delay_ms 函数,然后delay_ms 函数调用了delay_us 函数。这样在一些复杂的代码里面(尤其是第三方代码),可以很容易捋出函数调用关系并查看其局部变量的值,有助于我们分析代码解决问题。
关于DAP 的仿真调试,我们暂时就讲这么多。

仿真调试注意事项

在这里插入图片描述
又得重设的麻烦。
2,关于STM32 软件仿真,老版本的教程,我们给大家介绍过如何使用MDK 进行STM32软件仿真,由于其限制较多(只支持部分F1 型号),而且仿真器越来越便宜,硬件仿真更符合实际调试需求,调试效果更好。所以后续我们只介绍硬件仿真,不再推荐大家使用软件仿真了!
3,仿真调试找bug 是一个软件工程师必备的基本技能。MDK 提供了很多工具和窗口来辅助我们找问题,只要多使用,多练习,肯定就可以把仿真调试学好。这对我们后续的独立开发项目,非常有帮助。因此极力推荐大家多练习使用仿真器查找代码bug,学会这个基本技能。
4,调试代码不要浅尝辄止,要想尽办法找问题,具体的思路:先根据代码运行的实际现象分析问题,确定最可能出问题的地方,然后在相应的位置放置断点,查看变量,查看寄存器,分析运行状态和预期结果是否一致?从而找到问题原因,解决bug。特别提醒:
一定不要浅尝辄止,很多朋友只跟踪到最上一级函数,就说死机了,不会跟踪进去找问题!所以一定要一层层进入各种函数,越是底层(甚至汇编级别),越好找到问题原因。

MDK5 使用技巧

文本美化

文本美化,主要是设置一些关键字、注释、数字等的颜色和字体。如果你刚装MDK,没进行字体颜色配置,以跑马灯例程为例,你的界面效果如图4.4.1.1 所示:

在这里插入图片描述
上图是MDK 默认的设置,可以看到其中的关键字和注释等字体的颜色不是很漂亮,而MDK 提供了我们自定义字体颜色的功能。我们可以在工具条上点击(配置对话框)弹出如图4.4.1.2 所示界面:

在这里插入图片描述
①设置代码编辑器字体使用:Chinese GB2312(Simplified),以更好的支持中文。
②设置编辑器的空格可见:View White Space,所有空格使用“.”替代,TAB 使用“→”替代,这样可以方便我们对代码进行对齐操作。同时,我们推荐所有的对齐都用空格来替代,这样在不同软件之间查看源代码,就不会引起由于TAB 键大小不一样导致代码不对齐的问题,方便使用不同软件查看和编辑代码。
③设置C/C++文件,TAB 键的大小为4 个字符,且字符使用空格替代(Insert spaces for tabs)。这样我们在使用TAB 键进行代码对齐操作的时候,都会用空格替代,保证不同软件使用代码都可以对齐。
然后,选择:Colors & Fonts 选项卡,在该选项卡内,我们就可以设置自己的代码的字体和颜色了。由于我们使用的是C 语言,故在Window 下面选择:C/C++ Editor Files 在右边就可以看到相应的元素了。如图4.4.1.3 示:

在这里插入图片描述

然后点击各个元素(Element)修改为你喜欢的颜色(注意双击,且有时候可能需要设置多次才生效,MDK 的bug),当然也可以在Font 栏设置你字体的类型,以及字体的大小等。
然后,点击User Keywords 选项卡,设置用户定义关键字,以便用户自定义关键字也显示对应的颜色(对应图4.4.1.3 中的User Keyword/Lable 颜色)。在User Keywords 选项卡对话框下面输入你自己定义的关键字,如图4.4.1.4 示:

在这里插入图片描述

这里我们设置了uint8_t、uint16_t 和uint32_t 等三个用户自定义关键字,相当于unsigned char、unsigned short 和unsigned int。如果你还有其他自定义关键字,在这里添加即可。设置成之后,点击OK,就可以在主界面看到你所修改后的结果,例如我修改后的代码显示效果如图4.4.1.5 示:
在这里插入图片描述

这就比开始的效果好看一些了。字体大小,则可以直接按住:ctrl+鼠标滚轮,进行放大或者缩小,或者也可以在刚刚的配置界面设置字体大小。
同时,上图中可以看到空白处有很淡的一些……显示,这就是我们勾选了View White Space选项后体现出来的效果,可以方便我们对代码进行规范对齐整理。一开始看的时候可能有点不习惯,看多了就习惯了,大家慢慢适应就好了。
其实在这个编辑配置对话框里,还可以对其他很多功能进行设置,比如动态语法检测等,我们将4.4.2 节介绍。

语法检测&代码提示

在这里插入图片描述
在这里插入图片描述

Strut / Class Members,用于开启结构体/类成员提示功能。
Function Parameters,用于开启函数参数提示功能。
Symbols after xx characters,用于开启代码提示功能,即在输入多少个字符以后,提示匹配的内容(比如函数名字、结构体名字、变量名字等),这里默认设置3 个字符以后,就开始提示。

如图4.4.2.2 所示:

在这里插入图片描述
在这里插入图片描述

这几个功能,对我们编写代码很有帮助,可以加快代码编写速度,并且及时发现各种问题。
不过这里要提醒大家,语法动态检测这个功能,有的时候会误报(比如sys.c 里面,就有误报),大家可以不用理会,只要能编译通过(0 错误,0 警告),这样的语法误报,一般直接忽略即可。

代码编辑技巧(TAB键设置、函数定义跳转、快速注释、快速打开头文件、查找替换)

这里给大家介绍几个我常用的技巧,这些小技巧能给我们的代码编辑带来很大的方便,相信对你的代码编写一定会有所帮助。

  1. TAB 键的妙用
    首先要介绍的就是TAB 键的使用,这个键在很多编译器里面都是用来空位的,每按一下移空几个位。如果你是经常编写程序的对这个键一定再熟悉不过了。但是MDK 的TAB 键和一般编译器的TAB 键有不同的地方,和C++的TAB 键差不多。MDK 的TAB 键支持块操作。也就是可以让一片代码整体右移固定的几个位,也可以通过SHIFT+TAB 键整体左移固定的几个位。

假设我们前面的串口1 中断回调函数如图4.4.3.1 所示:

在这里插入图片描述

上图的代码很不规范,这还只是短短的30 来行代码,如果你的代码有几千行,全部是这个样子,不头大才怪。这时我们就可以通过TAB 键的妙用来快速修改为比较规范的代码格式。
选中一块然后按TAB 键,你可以看到整块代码都跟着右移了一定距离,如图4.4.3.2 所示:

在这里插入图片描述

接下来我们就是要多选几次,然后多按几次TAB 键就可以达到迅速使代码规范化的目的,最终效果如图4.4.3.3 所示

在这里插入图片描述

图4.4.3.3 中的代码相对于图4.4.3.1 中的要好看多了,经过这样的整理之后,整个代码一下就变得有条理多了,看起来很舒服。

  1. 快速定位函数/变量被定义的地方
    上一节,我们介绍了TAB 键的功能,接下来我们介绍一下如何快速查看一个函数或者变量所定义的地方。

在这里插入图片描述

大家在调试代码或编写代码的时候,一定有想看看某个函数是在那个地方定义的,具体里面的内容是怎么样的,也可能想看看某个变量或数组是在哪个地方定义的等。尤其在调试代码或者看别人代码的时候,如果编译器没有快速定位的功能的时候,你只能慢慢的自己找,代码量比较少还好,如果代码量一大,那就郁闷了,有时候要花很久的时间来找这个函数到底在哪里。型号MDK 提供了这样的快速定位的功能。只要你把光标放到这个函数/变量(xxx)的上面(xxx 为你想要查看的函数或变量的名字),然后右键,可以看到如图4.4.3.4 所示的菜单栏。

在图4.4.3.4 中,我们找到Go to Definition Of‘sys_stm32_clock_init’这个地方,然后单击左键就可以快速跳到sys_stm32_clock_init 函数的定义处(注意要先在Options for Target 的Output 选项卡里面勾选Browse Information 选项,再编译,再定位,否则无法定位!)。如图4.4.3.5 所示:
在这里插入图片描述
在这里插入图片描述

  1. 快速注释与快速消注释
    接下来,我们介绍一下快速注释与快速消注释的方法。在调试代码的时候,你可能会想注释某一片的代码,来看看执行的情况,MDK 提供了这样的快速注释/消注释块代码的功能。也是通过右键实现的。这个操作比较简单,就是先选中你要注释的代码区,然后右键,选择Advanced→Comment Selection 就可以了。

以led_init 函数为例,比如我要注释掉下图中所选中区域的代码,如图4.4.3.6 所示:

在这里插入图片描述

我们只要在选中了之后,选择右键,再选择Advanced→Comment Selection 就可以把这段代码注释掉了。执行这个操作以后的结果如图4.4.3.7 所示:

在这里插入图片描述

这样就快速的注释掉了一片代码,而在某些时候,我们又希望这段注释的代码能快速的取消注释,MDK 也提供了这个功能。与注释类似,先选中被注释掉的地方,然后通过右键→Advanced,不过这里选择的是Uncomment Selection。

除了前面介绍的几个比较常用的技巧,这里还介绍几个其他的小技巧,希望能让你的代码编写如虎添翼。

第一个是快速打开头文件。在将光标放到要打开的引用头文件上,然后右键选择Open Document“XXX”,就可以快速打开这个文件了(XXX 是你要打开的头文件名字)。如图4.4.4.1所示:
在这里插入图片描述

第二个小技巧是查找替换功能。这个和WORD 等很多文档操作的替换功能是差不多的,在MDK 里面查找替换的快捷键是“CTRL+H”,只要你按下该按钮就会调出如图4.4.4.2 所示界面:

在这里插入图片描述

这个替换的功能在有的时候是很有用的,它的用法与其他编辑工具或编译器的差不多,相信各位都不陌生了,这里就不啰嗦了。

在这里插入图片描述
在这里插入图片描述

该方法可以很方便的查找各种函数/变量,而且可以限定搜索范围(比如只查找.c 文件和.h文件等),是非常实用的一个技巧。

新建寄存器版本MDK 工程

通过前面几章的学习,我们对STM32 有了个比较清晰的了解,本章我们将讲解新建寄存器库版本MDK 工程的详细步骤。我们把本章新建好的工程放在光盘里,路径:4,程序源码\2,标准例程-HAL 库版本\实验0 基础入门实验\实验0-2,新建最工程实验-寄存器版本,大家在学习新建工程过程中间遇到一些问题,可以直接打开这个工程,然后对比学习。

新建寄存器版本MDK 工程

本节我们将教大家如何新建一个STM32F103 的MDK5 工程。为了方便大家参考,我们将本节最终新建好的工程模板存放在A 盘:4、程序源码\2,标准例程-HAL 库版本\实验0 基础入门实验\实验0-2,新建工程实验-寄存器版本,如遇新建工程问题,请打开该实验对比。
整个新建过程比较复杂,我们将其拆分为5 个步骤进行讲解,请准备大概2 个小时时间,耐心细致的做完!对你后续的学习非常有帮助!
在新建工程之前,首先我们要做如下准备:

1,STM32Cube 官方固件包:我们使用的固件包版本是STM32Cube_FW_F1_V1.8.3,固件包路径:A 盘→8,STM32 参考资料→1,STM32CubeF1 固件包。
1,开发环境搭建:参考本书第三章相关内容。

新建工程文件夹

新建工程文件夹分为2 个步骤:1,新建工程文件夹;2,拷贝工程相关文件。

1. 新建工程文件夹
首先我们在桌面新建一个工程根目录文件夹,后续的工程文件都将在这个文件夹里建立,我们把这个文件夹重命名为:实验0-2,新建工程实验-寄存器版本。如图6.1.1.1 所示:

在这里插入图片描述

为了让工程的文件目录结构更加清晰易懂,我们会在工程根目录文件夹下建立以下几个文件夹,每个文件夹名称及其作用如表6.1.1.1 所示:

在这里插入图片描述

新建完成以后,最后得到我们的工程根目录文件夹如图6.1.1.2 所示。

在这里插入图片描述

工程根目录及其相关文件夹新建好以后,我们需要拷贝一些工程相关文件过来(主要是在Drivers 文件夹里面),以便等下的新建工程需要。

2. 拷贝工程相关文件

接下来,我们按图6.1.1.2 的根目录文件夹顺序介绍每个文件夹及其需要拷贝的文件。

Drivers 文件夹
该文件夹用于存放与硬件相关的驱动层文件,一般包括如表6.1.1.2 所示的三个文件夹:

在这里插入图片描述

BSP 文件夹:用于存放正点原子提供的板级支持包驱动代码,如:LED、蜂鸣器、按键等。本章我们暂时用不到该文件夹,不过可以先建好备用。

CMSIS 文件夹:用于存放CMSIS 底层代码(ARM 和ST 提供),如:启动文件(.s 文件)、stm32f1xx.h 等各种头文件。该文件夹我们可以直接从STM32CubeF1 固件包(路径:A 盘→8,STM32 参考资料→1,STM32CubeF1 固件包)里面拷贝,不过由于固件包里面的CMISIS 兼容了太多芯片,导致非常大(100 多MB),因此我们根据实际情况,对其进行了大幅精简,精简后的CMSIS 文件夹大小为1MB 左右。精简后的CMSIS 文件夹大家可以在:A 盘→4,程序源码→1,标准例程-寄存器版本文件夹里面的任何一个实验的Drivers 文件夹里面拷贝过来。

SYSTEM 文件夹:用于存放正点原子提供的系统级核心驱动代码,如:sys.c、delay.c 和usart.c 等,方便大家快速搭建自己的工程。该文件同样可以从:A 盘→4,程序源码→1,标准例程-寄存器版本文件夹里面的任何一个实验的Drivers 文件夹里面拷贝过来。

执行完以上操作后,Drivers 文件夹最终结构如图6.1.1.3 所示:

在这里插入图片描述

Middlewares 文件夹
该文件夹用于存放正点原子和其他第三方提供的中间层代码(组件/Lib 等),如:USMART、MALLOC、TEXT、FATFS、USB、LWIP、各种OS、各种GUI 等等。本章我们暂时用不到该文件夹,不过可以先建好备用,后面的实验将会陆续添加各种文件。

Output 文件夹
该文件夹用于存放编译器编译工程输出的中间文件,比如:.hex、.bin、.o 文件等等。这里不需要操作,后面只需要在MDK 里面设置该文件夹为编译过程中间文件的存放文件夹就行。

Projects 文件夹
该文件夹用于存放编译器(MDK、IAR 等)工程文件,我们主要用MDK,为了方便区分,我们在该文件夹下新建:MDK-ARM 文件夹,用于存放MDK 的工程文件,如图6.1.1.4 所示:

在这里插入图片描述

User 文件夹
该文件夹用于存放用户编写的代码,如:main.c 等。目前还没有任何代码,所以暂时为空即可。

新建一个工程框架

首先,打开MDK 软件。然后点击Project→New uVision Project 如图6.1.2.1 所示:
在这里插入图片描述
然后弹出工程命名和保存的操作窗口,我们将工程文件保存路径设置在上一节新建的工程文件夹内,具体路径为:桌面→实验0-2,新建工程实验-寄存器版本→Projects→MDK-ARM,工程名字我们取:atk_f103,最后点击保存即可。具体操作窗口如图6.1.2.2 所示:

在这里插入图片描述

之后,弹出器件选择对话框,如图6.1.2.3 所示。因为战舰开发板所使用的STM32 型号为STM32F103ZET6,所以我们选择:STMicroelectronics→STM32F1 Series→STM32F103→STM32F103ZE(如果使用的是其他系列的芯片,选择相应的型号就可以了,特别注意:一定要安装对应的器件pack 才会显示这些内容哦!!如果没得选择,请关闭MDK,然后安装A 盘:6,软件资料\1,软件\MDK5\ Keil.STM32F1xx_DFP.2.3.0.pack 这个安装包后重试)。

在这里插入图片描述

点击OK,MDK 会弹出Manage Run-Time Environment 对话框,如图6.1.2.4 所示:

在这里插入图片描述

这是MDK5 新增的一个功能,在这个界面,我们可以添加自己需要的组件,从而方便构建开发环境,不过这里我们不做介绍。所以在图6.1.2.4 所示界面,我们直接点击Cancel,即可,得到如图6.1.2.5 所示界面:

在这里插入图片描述

此时,我们打开MDK-ARM 文件夹,会看到MDK 在该文件夹下自动创建了3 个文件(DebugConfig、Listings 和Objects),如图6.1.2.6 所示:
在这里插入图片描述

这三个文件夹的作用如表6.1.2.1 所示:

在这里插入图片描述
编译过程产生的链接列表、调试信息、预览、lib 等文件,统称为中间文件。为了统一管理,方便使用,我们会把输出在Listings 和Objects 文件夹的内容,统一改为输出到Output 文件夹(通过魔术棒设置),我们先把MDK 自动生成的这两个文件夹(Listings 和Objects)删除。

至此,我们还只是建了一个框架,还有好几个步骤要做,比如添加文件、魔术棒设置、编写main.c 等。

设置工程名和分组名、添加启动文件、添加 SYSTEM 源码

本节将分3 个步骤:1,设置工程名和分组名;2,添加启动文件;3,添加SYSTEM 源码。

1. 设置工程名和分组名

在Project→Target 上右键,选择Manage Project Items…(方法一)或在菜单栏点击品字形红绿白图标(方法二)进入工程管理界面,如图6.1.3.1 所示:

在这里插入图片描述

在工程管理界面,我们可以执行设置工程名字(Project Targets)、分组名字(Groups)以及添加每个分组的文件(Files)等操作。我们设置工程名字为:Template,并设置四个分组:

Startup(存放启动文件)、User(存放main.c 等用户代码)、Drivers/SYSTEM(存放系统级驱动代码)、Readme(存放工程说明文件),如图6.1.3.2 所示:

在这里插入图片描述

设置好之后,我们点击OK,回到MDK 主界面,可以看到我们设置的工程名和分组名如图6.1.3.3 所示:

在这里插入图片描述

这里我们只是新建了一个简单的工程,并没有添加BSP、Middlewares 等分组,后面随着工程复杂程度的增加,我们需要一步步添加对应的分组。

注意:为了让工程结构清晰,我们会尽量让MDK 的工程分组和我们前面新建的工程文件夹对应起来,由于MDK 分组不支持多级目录,因此我们将路径也带入分组命名里面,以便区分。如:User 分组对应User 文件夹里面的源码,Drivers/SYSTEM 分组,对应Drivers/SYSTEM文件夹里面的源码,Drivers/BSP 分组对应Drivers/BSP 文件夹里面的源码等。

2. 添加启动文件

启动文件(.s 文件)包含STM32 的启动代码,其主要作用包括:1、堆栈(SP)的初始化;2、初始化程序计数器(PC);3、设置向量表异常事件的入口地址;4、调用main 函数等,是每个工程必不可少的一个文件,我们在本书第九章会有详细介绍。
该文件由ST 官方提供,对于STM32F103 来说有4 个启动文件可选,如表6.1.3.1 所示:

在这里插入图片描述

启动文件存放在STM32CubeF1 软件包的:Drivers→CMSIS→Device→ST→STM32F1xx→Source→Templates→arm 文件夹下。因为我们开发板使用的是STM32F103ZET6,对应的启动文件为:startup_stm32f103xe.s,为了节省空间,在精简版CMSIS 文件夹里面我们把其他启动文件都删了。而且,为了更好的匹配寄存器版本代码,我们对startup_stm32f103xe.s 做了2 处修改:
1,我们用不到编译器的内存管理函数,为节省内存,将Heap_Size 改成0,源码如下:

;未用到编译器自带的内存管理(malloc,free等),设置Heap_Szie为0
Heap_Size EQU 0x00000000

2,寄存器代码不需要调用SystemInit 函数,因此修改Reset_Handler 函数,去掉SystemInit调用,源码如下:

Reset_Handler PROC
				EXPORT Reset_Handler 	[WEAK]
				IMPORT __main
				;寄存器版本代码,因为没有用到SystemInit函数,所以注释掉以下代码为防止报错!
				;HAL库版本代码,建议加上这里(提供SystemInit函数),以初始化stm32时钟等。
				;IMPORT SystemInit
				;LDR 	R0, =SystemInit
				;BLX 	R0
				
				LDR 	R0, =__main
				BX 		R0
				ENDP

关于启动文件的说明,我们就介绍这么多,接下来我们看如何添加启动文件到工程里面。我们有两种方法给MDK 的分组添加文件:1,双击Project 下的分组名添加。2,进入工程管理界面添加。

这了我们使用方法1 添加(路径:实验0-2,新建工程实验-寄存器版本\Drivers\CMSIS\Device\ST\STM32F1xx\Source\Templates\arm),如图6.1.3.4 所示:

在这里插入图片描述

上图中,我们也可以点击Add 按钮进行文件添加。添加完后,点击Close,完成启动文件添加,得到工程分组如图6.1.3.5 所示:

在这里插入图片描述

3. 添加SYSTEM 源码

这里我们在工程管理界面(方法2)进行SYSTEM 源码添加。点击:按钮,进入工程管理界面,选中Drivers/SYSTEM 分组,然后点击:Add Files,进入文件添加对话框,依次添加delay.c、sys.c 和usart.c 到该分组下,如图6.1.3.6 所示:

在这里插入图片描述

注意:这些源码都是在第6.1.1 小节的第二步拷贝过来的,如果之前没拷贝,是找不到这些源码的。添加完成后,如图6.1.3.7 所示:

在这里插入图片描述

魔术棒设置(Target选项卡、Output选项卡、Listing选项卡、C/C++选型卡、Debug选项卡、Utilities选项卡)

为避免编写代码和编译报错,我们需要通过魔术棒对MDK 工程进行相关设置。在MDK主界面,点击:(魔术棒图标,即Options for Target 按钮),进入工程设置对话框,我们将进行如下几个选项卡的设置。

1. 设置Target 选项卡
在魔术棒→Target 选项卡里面,我们进行如图6.1.4.1 所示设置:

在这里插入图片描述

上图中,我们设置芯片所使用的外部晶振频率为8Mhz,选择ARM Compiler 版本为:Use default compiler version 5(即AC5 编译器)。
这里我们说明一下AC5 和AC6 编译的差异,如表6.1.4.1 所示:

在这里插入图片描述

由于AC5 对中文支持比较好,且兼容性相对好一点,为了避免不必要的麻烦,我们推荐大家使用AC5 编译器。为了让大家自由选择,我们正点原子的源码,绝大部分也是支持AC6编译器的,不过在选项卡设置上稍有差异,具体差异如表6.1.4.2 所示:

在这里插入图片描述

2. 设置Output 选项卡
在魔术棒→Output 选项卡里面,进行如图6.1.4.2 所示设置:

在这里插入图片描述

注意,我们勾选:Browse Information,用于输出浏览信息,这样就可以使用go to definition查看函数/变量的定义,对我们后续调试代码比较有帮助,如果不需要调试代码,则可以去掉这个勾选,以提高编译速度。

3. 设置Listing 选项卡
在魔术棒→Listing 选项卡里面,进行如图6.1.4.3 所示设置:

在这里插入图片描述

经过Output 和Listing 这两步设置,原来存储在Objects 和Listings 文件夹的内容(中间文件)就都改为输出到Output 文件夹了。

4. 设置C/C++选项卡
在魔术棒→C/C++选项卡里面,进行如图6.1.4.4 所示设置:

在这里插入图片描述

在②处设置了全局宏定义:STM32F103xE,用于定义所用STM32 型号,在stm32f1xx.h 里面会用到该宏定义。
在③处设置了优化等级为-O0,可以得到最好的调试效果,当然为了提高优化效果提升性能并降低代码量,可以设置-O1~-O3,数字越大效果越明显,不过也越容易出问题。注意:当使用AC6 编译器的时候,这里推荐默认使用-O1 优化。
在④处勾选C99 模式,即使用C99 C 语言标准。
在⑤处,我们可以进行头文件包含路径设置,点击此按钮,进行如图6.1.4.5 所示设置:

在这里插入图片描述

上图中我们设置了4 个头文件包含路径,其中3 个在Drivers 文件夹下,一个在User 文件夹下。为避免频繁设置头文件包含路径,正点原子最新源码的include 全部使用相对路径,也就是我们只需要在头文件包含路径里面指定一个文件夹,那么该文件夹下的其他文件夹里面的源码,如果全部是使用相对路径,则无需再设置头文件包含路径了,直接在include 里面就指明了头文件所在。

关于相对路径,这里大家记住3 点:

1,默认路径就是指MDK 工程所在的路径,即.uvprojx 文件所在路径(文件夹)
2,“./”表示当前目录(相对当前路径,也可以写做“.\”)
3,“…/”表示当前目录的上一层目录(也可以写做“…\”)

举例来说,上图中:…\Drivers\CMSIS\Device\ST\STM32F1xx\Include,前面两个“…\”,表示Drivers 文件夹在当前MDK 工程所在文件夹(MDK-ARM)的上2 级目录下,具体解释如图6.1.4.6 所示:

在这里插入图片描述

上图表示根据头文件包含路径:…\Drivers\CMSIS\Device\ST\STM32F1xx\Include,编译器可以找到⑥处所包含的这些头文件,即代码里面可以直接include 这些头文件使用。

再举个例子,在完成如图6.1.4.5 所示的头文件包含路径设置以后,我们在代码里面编写:

#include "./SYSTEM/sys/sys.h"

即表示当前头文件包含路径所指示的4 个文件夹里面,肯定有某一个文件夹包含了:SYSTEM/sys/sys.h 的路径,实际上就是在Drivers 文件夹下面,两者结合起来就相当于:

#include "../../Drivers/SYSTEM/sys/sys.h"

这就是相对路径。它既可以减少头文件包含路径设置(即减少MDK 配置步骤,免去频繁设置头文件包含路径的麻烦),同时又可以很方便的知道头文件具体在那个文件夹,因此我们推荐在编写代码的时候使用相对路径。

关于相对路径,我们就介绍这么多,大家搞不明白的可以在网上搜索相关资料学习,也可以在后面的学习,分析我们其他源码,慢慢体会,总之不难,但是好用。

最后,我们如果使用AC6 编译器,则在图6.1.4.4 的Misc Controls 处需要设置:-Wno-invalid-source-encoding,避免中文编码报错,如果使用AC5 编译器,则不需要该设置。

5. 设置Debug 选项卡
在魔术棒→Debug 选项卡里面,进行如图6.1.4.7 所示设置:

在这里插入图片描述

图中,我们选择使用:CMSIS-DAP 仿真器,使用SW 模式,并设置最大时钟频率为10Mhz,以得到最高下载速度。当我们将仿真器和开发板连接好,并给开发板供电以后,仿真器就会找到开发板芯片,并在SW Device 窗口显示芯片的IDCODE、Device Name 等信息(图中⑤处),当无法找到时,请检查供电和仿真器连接状况。

6. 设置Utilities 选项卡
在魔术棒→Debug 选项卡里面,进行如图6.1.4.8 所示设置:
在这里插入图片描述

图中⑥处下载算法,是MDK 默认添加的,针对STM32F10x 大容量系列产品(FLASH 容量在256KB~512KB 之间)。一般我们用这个即可。如果⑥处没有下载算法,则点击⑦处按钮,执行添加一下下载算法即可(名字和⑥处的算法名字一样)。

添加main.c,并编写代码

在MDK 主界面,点击:,新建一个main.c 文件,并保存在User 文件夹下。然后双击User 分组,弹出添加文件的对话框,将User 文件夹下的main.c 文件添加到User 分组下。得到如图6.1.5.1 所示的界面:

在这里插入图片描述
至此,我们就可以开始编写我们自己的代码了。我们在main.c 文件里面输入如下代码:

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
int main(void)
{
	uint8_t t = 0;
	sys_stm32_clock_init(9); /* 设置时钟, 72Mhz */
	delay_init(72); /* 延时初始化*/
	usart_init(72, 115200); /* 串口初始化*/
	while (1)
	{
		printf("t:%d\r\n", t);
		delay_ms(500);
		t++;
	}
}

此部分代码,在A 盘→4,程序源码→1,标准例程-寄存器版本→ 实验0 基础入门实验→实验0-2,新建最工程实验-寄存器版本→User→main.c 里面有,大家可以自己输入,也可以直接拷贝。强烈建议自己输入,以加深对程序的理解和印象!!
注意,这里的include 就是使用的相对路径,关于相对路径,请参考前面C/C++选项卡设置章节进行学习。

输入完代码,如图6.1.5.2 所示

在这里插入图片描述

编写完main.c 以后,我们点击:(Rebuild)按钮,编译整个工程,编译结果如图6.1.5.3
所示:

在这里插入图片描述

编译结果提示:代码总大小(Porgram Size)为:FLASH 占用2444 字节(Code + RO + RW),SRAM 占用1336 字节(RW + ZI);并成功创建了Hex 文件(可执行文件,放在Output 目录下);
编译0 错误,0 警告。

注意:如果编译提示有错误/警告,请根据提示,从第一个错误/警告开始解决,直到0 错误0 警告。如果出错,很有可能是之前的操作存在问题,请对照教程找问题。

另外,我们在Readme 分组下还没有添加任何文件,由于只是添加一个说明性质的文件(.txt),并不是工程必备文件,因此这里我们就不添加了,开发板光盘的源码我们是有添加的,大家可以去参考一下。
至此,新建寄存器版本MDK 工程完成。

下载验证

有两种方法可以给STM32F103 芯片下载代码:1,使用串口下载;2,使用仿真器下载。这

在这里插入图片描述

上图提示:Application running…,则表示代码下载成功,且开始运行。此时,我们打开串口调试助手,并设置好端口号(COM 号)和波特率(115200),就可以看到打印出来的t 值,如图6.2.2 所示:

在这里插入图片描述
说明我们的程序运行正常,下载验证无误。

认识HAL库

HAL,英文全称Hardware Abstraction Layer,即硬件抽象层。HAL 库是ST 公司提供的外设驱动代码的驱动库,用户只需要调用库的API 函数,便可间接配置寄存器。我们要写程序控制STM32 芯片,其实最终就是控制它的寄存器,使之工作在我们需要的模式下,HAL 库将大部分寄存器的操作封装成了函数,我们只需要学习和掌握HAL 库函数的结构和用法,就能方便地驱动STM32 工作,以节省开发时间。

初识STM32 HAL 库

STM32 开发中常说的HAL 库开发,指的是利用HAL 库固件包里封装好的c 语言编写的驱动文件,来实现对STM32 内部和外围电器元件的控制的过程。但只有HAL 库还不能直接驱动一个STM32 的芯片,其它的组件已经由ARM 与众多芯片硬件、软件厂商制定的通用的软件开发标准CMSIS 实现了,本文只简单介绍这个标准,等大家熟悉开发后再研究这个框架。
简单地了解HAL 库的发展和作用,可以方便学习者确定HAL 库是否适合作为学习者自己长期开发STM32 的工具,以降低开发、学习的成本。

CMSIS 标准

根据一些调查研究表明,软件开发已经被嵌入式行业公认为最主要的开发成本,为了降低这个成本,ARM 与Atmel、IAR、KEIL、SEGGER 和ST 等诸多芯片和软件工具厂商合作,制定了一个将所有Cortex 芯片厂商的产品的软件接口标准化的标准CMSIS(Cortex Microcontroller Software Interface Standard)。下面来看ARM 官方提供的CMSIS 规范架构,如图7.1.1.1 所示:
在这里插入图片描述
从图中可以看出这个标准分级明显,从用户程序到内核底层实现做了分层。按照这个分级,HAL 库属于CMSIS-Pack 中的“Peripheral HAL”层。CMSIS 规定的最主要的3 个部分为:核内外设访问层(由ARM 负责实现)、片上外设访问层和外设访问函数(后面两个由芯片厂商负责实现)。ARM 整合并提供了大量的模版,各厂商根据自己的芯片差异修改模版,这其中包括汇编文件startup_device.s、system_.h 和system_.c 这些与初始化和系统相关的函数。
结合STM32F1 的芯片来说,其CMSIS 应用程序的简单结构框图,不包括实时操作系统和中间设备等组件,其结构如图7.1.1.2 所示。

在这里插入图片描述

上面的框架是根据我们现在已经学习到的知识回过头来作的一个总结,这里只是作简单的介绍,告诉大家它们之间存在一定联系,关于组成这些部分的文件、文件的作用及各文件如何组合、各分层的作用和意义,我们会在今后的学习过程中慢慢学习。

HAL 库简介

库函数的引入,大大降低了STM 主控芯片开发的难度。ST 公司为了方便用户开发STM32芯片开发提供了三种库函数,从时间产生顺序是:标准库、HAL 库和LL 库。目前ST 已经逐渐暂停对部分标准库的支持,ST 的库函数维护重点对角已经转移到HAL 库和LL 库上,下面我们分别为这三种库作一下简单的介绍。

  1. 标准外设库(Standard Peripheral Libraries)
    标准外设库(Standard Peripherals Library)是对STM32 芯片的一个完整的封装,包括所有标准器件外设的器件驱动器,是ST 最早推出的针对STM 系列主控的库函数。标准库的设计的初衷是减少用户的程序编写时间,进而降低开发成本。几乎全部使用C 语言实现并严格按照“Strict ANSI-C”、MISRA-C 2004 等多个C 语言标准编写。但标准外设库仍然接近于寄存器操作,主要就是将一些基本的寄存器操作封装成了C 函数。开发者仍需要关注所使用的外设是在哪个总线之上,具体寄存器的配置等底层信息。
    在这里插入图片描述

ST 为各系列提供的标准外设库稍微有些区别。例如,STM32F1x 的库和STM32F3x 的库在文件结构上就有些不同,此外,在内部的实现上也稍微有些区别,这个在具体使用(移植)时,需要注意一下!但是,不同系列之间的差别并不是很大,而且在设计上是相同的。STM32 的标准外设库涵盖以下3 个抽象级别:
⚫ 包含位域和寄存器在内的完整的寄存器地址映射
⚫ 涵盖所有外围功能(具有公共API 的驱动器)的例程和数据结构的集合。
⚫ 一组包含所有可用外设的示例,其中包含最常用的开发工具的模板项目。
关于更详细的信息,可以参考ST 的官方文档《STM32 固件库使用手册中文翻译版》,文档中对于标准外设库函数命名、文件结构等都有详细的说明,这里我们就不多介绍了。
值得一提的是由于STM32 的产品性能及标准库代码的规范和易读性以及例程的全覆盖性,使STM32 的开发难度大大下降。但ST 从L1 以后的芯片L0、L4 和F7 等系列就没有再推出相应的标准库支持包了。
2. HAL 库
HAL 是Hardware Abstraction Layer 的缩写,即硬件抽象层。是ST 为可以更好的确保跨STM32 产品的最大可移植性而推出的MCU 操作库。这种程序设计由于抽离应用程序和硬件底层的操作,更加符合跨平台和多人协作开发的需要。
HAL 库是基于一个非限制性的BSD 许可协议(Berkeley Software Distribution)而发布的开源代码。ST 制作的中间件堆栈(USB 主机和设备库,STemWin)带有允许轻松重用的许可模式,只要是在ST 公司的MCU 芯片上使用,库中的中间件(USB 主机/设备库,STemWin)协议栈即被允许修改,并可以反复使用。至于基于其它著名的开源解决方案商的中间件(FreeRTOS,FatFs,LwIP 和PolarSSL)也都具有友好的用户许可条款。
HAL 库是从ST 公司从自身芯片的整个生产生态出发,为了方便维护而作的一次整合,以改变标准外设库带来各系列芯片操作函数结构差异大、分化大、不利于跨系列移植的情况。相比标准外设库,STM32Cube HAL 库表现出更高的抽象整合水平,HAL 库的API 集中关注各外设的公共函数功能,这样便于定义一套通用的用户友好的API 函数接口,从而可以轻松实现从一个STM32 产品移植到另一个不同的STM32 系列产品。但由于封闭函数为了适应最大的兼容性,HAL 库的一些代码实际上的执行效率要远低于寄存器操作。但即便如此,HAL 库仍是ST未来主推的库。
3. LL 库:
LL 库(Low Layer)目前与HAL 库捆绑发布,它设计为比HAL 库更接近于硬件底层的操作,代码更轻量级,代码执行效率更高的库函数组件,可以完全独立于HAL 库来使用,但LL库不匹配复杂的外设,如USB 等。所以LL 库并不是每个外设都有对应的完整驱动配置程序。

使用LL 库需要对芯片的功能有一定的认知和了解,它可以:
•独立使用,该库完全独立实现,可以完全抛开HAL 库,只用LL 库编程完成。
•混合使用,和HAL 库结合使用。
对于HAL 库和LL 库的关系,如图7.1.2.2 CubeF1 的软件框架所示,可以看出它们设计为彼此独立的分支,但又同属于HAL 库体系。

在这里插入图片描述
通过以上简介我们对目前主流的STM32 开发库有了一个初步的印象。标准库和HAL 库、
LL 库完全相互独立,HAL 库更倾向于外设通用化,扩展组件中解决芯片差异操作部分;LL 倾向于最简单的寄存器操作,ST 在未来还将重点维护和建设HAL 库,标准库已经部分停止更新。
HAL 库和LL 库的应用将是未来的一个趋势。

HAL 库能做什么

用过标准库的朋友应该知道,使用标准库可以忽略很多芯片寄存器的细节,根据提供的接口函数快速配置和使用一个STM32 芯片,使用HAL 库也是如此。不论何种库,本质都是配置指定寄存器使芯片工作在我们需要的工作模式下。HAL 库在设计的时候会更注重软硬件分离。

HAL 库的API 集中关注各个外设的公共函数功能,便于定义通用性更好、更友好的API 函数接口,从而具有更好的可移植性。HAL 库写的代码在不同的STM32 产品上移植,非常方便。
我们需要学会调用HAL 库的API 函数,配置对应外设按照我们的要求工作,这就是HAL库能做的事。但是无论库封装得多高级,最终还是要通过配置寄存器来实现。所以我们学习HAL库的同时,也建议同时学习外设的工作原理和寄存器的配置。只有掌握了原理,才能更好的使用HAL 库,一旦发生问题也能更快速了定位和解决问题。

HAL 库还可以和STM32CubeMX(图形化软件配置工具)配套一起使用,开发者可以使用该工具进行可视化配置,并且自动生成配置好的初始化代码,大大的节省开发时间。

HAL 库驱动包

HAL 库是一系列封装好的驱动函数,本节将从下载渠道、固件包的内容分析及在实际开发中用到的几个文件的详细介绍。

如何获取HAL 库固件包

HAL 库是ST 推出的STM32Cube 软件生态下的一个分支。STM32Cube 是ST 公司提供的一套免费开发工具和STM32Cube 固件包,旨在通过减少开发工作、时间和成本来简化开发人员的工作,并且覆盖整个STM32 产品。它包含两个关键部分:
1、允许用户通过图形化向导来生成C 语言工程的图形配置工具STM32CubeMX。可以通过CubeMX 实现方便地下载各种软件或开发固件包。
2、包括由STM32Cube 硬件抽象层(HAL),还有一组一致的中间件组件(RTOS、USB、FAT 文件系统、图形、TCP/IP 和以太网),以及一系列完整的例程组成的STM32Cube 固件包。
ST 提供了多种获取固件包的方法。本节只介绍从ST 官方网站上直接获取固件库的方法。
网页登陆:www.st.com,在打开的页面中依次选择:
“Tools & Software→Ecosystem →STM32Cube →新页面→Prodcut selector”,如图7.2.1.1:
在这里插入图片描述
在展开的页面中选择我们需要和固件,这展开“STM32CubeF1”即可看到我们需要的F1 的安装包,按下图操作,在新的窗口中拉到底部,选择适合自己的下载方式,注册帐号即可获取相应的驱动包。
在这里插入图片描述
STM32Cube 固件包,我们已经给大家下载好并且放到A 盘→8,STM32 参考资料→1,STM32F1xx 固件库,当前固件包版本是:STM32Cube_FW_F1_V1.8.3(注意这个压缩包是1.8.3,但压缩包里的文件夹命名是1.8.0,后面讲到文件夹路径时我们按ST 给的1.8.0 来说明)。因为现在是STM32F103 的学习,所以我们准备好的固件包是F1 的。大家要根据自己学习的芯片,下载对应的固件包。如果需要最新的HAL 库固件,大家可按照上述方法到官网重新获取即可。

STM32Cube 固件包分析

STM32Cube 固件包完全兼容STM32CubeMX。对于图形配置工具STM32CubeMX 入门使用,由于需要STM32F1 基础才能入门使用,所以我们安排在后面第十章给大家讲解。本小节,我们主要讲解STM32Cube 固件包的结构。

解压缩后的STM32CubeF1 固件包的目录结构,如图7.2.2.1 所示。
在这里插入图片描述
下面对STM32CubeF1 固件包进行简要介绍。对于Documentation 文件夹,里面是一个STM32CubeF1 英文说明文档,是ST 官方指导如何使用HAL 库的指引。接下来我们通过几个表格依次来介绍一下STM32CubeF1 中几个关键的文件夹。
(1)Drivers 文件夹
Drivers 文件夹包含BSP,CMSIS 和STM32F1xx_HAL_Driver 三个子文件夹。三个子文件夹具体说明请参考下表7.2.2.1:

在这里插入图片描述
在这里插入图片描述
(2)Middlewares 文件夹
该文件夹下面有ST 和Third_Party 2 个子文件夹。ST 文件夹下面存放的是STM32 相关的一些文件,包括STemWin 和USB 库等。Third_Party 文件夹是第三方中间件,这些中间价都是非常成熟的开源解决方案。具体说明请见下表7.2.2.2:
在这里插入图片描述

(3)Projects 文件夹
该文件夹存放的是ST 官方的开发板的适配例程,每个文件夹对应一个ST 官方的Demo 板,根据型号的不同提供MDK 和IAR 等类型的例程。里面有很多实例,读者可以根据自己的需要来作为参考。
(4)Utilities 文件夹
该文件夹是一些公用组件,也是主要为ST 官方的DEMO 板提供的,在我们的例程中使用得不多。有兴趣的同学可以深入研究一下,这里我们不做过多介绍。
(5)其它几个文件
文件夹中还有几个单独的文件,用于声明软件版本或者版权信息,我们使用ST 的芯片已经默认得到这个软件的版权使用授权,可以简单了解一下各文件的内容,实际项目中我们一般不添加。
License.md:用于声明软件版权信息的文件。
package.xml:描述固件包版本信息的文件。
Release_Notes.html:超文本文件,用浏览器打开可知它是对固件包的补充描述和固件版本更新的记录说明。

CMSIS 文件夹关键文件

上一节中我们对STM32cube 固件包的主要目录结构做了分析。这一小节在上一小节的基础上,我们来分析一下CMSIS 文件夹:由命名可知,该文件夹和7.1.1 一小节中提到的CMSIS 标准是一致的,CMSIS 为软件包的内容制定了标准,包括文件目录的命名和内容构成,5.7.0 版本CMSIS 规定软件包目录如表7.2.3.1 所示
在这里插入图片描述
知道了CMSIS 规定的组件及其文件目录的大概内容后,我们再来看看ST 提供的CMSIS文件夹,如上节提到的,它的位置是“STM32Cube_FW_F1_V1.8.0\Drivers\CMSIS”。打开文件夹内容如图7.2.3.1 所示,可以发现它的目录结构完全按照CMSIS 标准执行,仅仅是作了部分删减。
在这里插入图片描述

CMSIS 文件夹中的Device 和Include 这两个文件夹中的文件是我们工程中最常用到的。下面对这两个文件夹作简单的介绍:

(1)Device 文件夹
Device 文件夹关键文件介绍如下表7.2.3.2 所示:
在这里插入图片描述

表7.2.3.2 列出的文件都是正式工程中必须的文件。固件包的CMSIS 文件包括了所有STM32F1 芯片型号的文件,而我们只用到STM32F103 系列,所以只针对我们用到的系列文件来讲。

(2)Include 文件夹
Include 文件夹存放了符合CMSIS 标准的Cortex-M 内核头文件。想要深入学习内核的朋友可以配合内核相关的手册去学习。对于STM32F1 的工程,我们只要把我们需要的添加到工程即可,需要的头文件有:cmsis_armcc.h、cmsis_armclang.h、cmsis_compiler.h、cmsis_version.h、core_cm3.h 和mpu_armv7.h。这几个头文件,对比起来,我们会比较多接触的是core_cm3.h。
core_cm3.h 是内核底层的文件,由ARM 公司提供,包含一些AMR 内核指令,如软件复位,开关中断等功能。今后在需要的例程再去讲解其程序,现在要提到的是它包含了一个重要的头文件stdint.h。

stdint.h 简介

stdint.h 是从c99 中引进的一个标准C 库的文件。在2000 年3 月,ANSI 采纳了C99 标准。ANSI C 被几乎所有广泛使用的编译器(如:MDK、IAR)支持。多数C 代码是在ANSI C基础上写的。任何仅使用标准C 并且不和硬件相关的代码,在任意平台上用遵循ANSI C 标准的编译器下能编译成功。就是说这套标准不依赖硬件,独立于任何硬件,可以跨平台。
stdint.h 可以在MDK 安装目录下找到,如MDK5 安装在C 盘时,可以在路径:
C:\Keil_v5\ARM\ARMCC\include 找到。stdint.h 的作用就是提供了类型定义,其部分类型定义代码如下:

/* 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;

在今后的程序,我们都将会使用这些类型,比如:uint32_t(无符号整型)、int16_t 等。

HAL 库框架结构

这一节我们将简要分析一下HAL 驱动文件夹下的驱动文件,帮助大家快速认识HAL 库驱动的构成,旨在帮大家快速认识HAL 库的函数的一些常用形式,帮助大家到遇到HAL 库时能根据名字大致推断该函数的用法,本部分不要求大家在学习完本节后完全记住。

HAL 库文件夹结构

HAL 库头文件和源文件在STM32Cube 固件包的STM32F1xx_HAL_Driver 文件夹中,打开该文件夹,如图7.3.1.1 所示。
在这里插入图片描述
STM32F1xx_HAL_Driver 文件夹下的Src(Source 的简写)文件夹存放是所有外设的驱动程序源码,Inc(Include 的简写)文件夹存放的是对应源码的头文件。Release_Notes.html 是HAL 库的版本更新信息。最后三个是库的用户手册,方便我们查阅对应库函数的使用。
打开Src 和Inc 文件夹,大家会发现基本都是stm32f1xx_hal_和stm32f1xx_ll_开头的.c和.h 文件。刚学HAL 库的朋友可能会说,stm32f1xx_hal_开头的是HAL 库,我能理解。那stm32f1xx_ll_开头的文件又是什么?这就告诉大家,stm32f1xx_ll_开头的文件是LL 库。

HAL 库文件介绍

HAL 库关键文件介绍如下表7.3.2.1 所示,表中ppp 代表任意外设。
在这里插入图片描述
在这里插入图片描述
以上是HAL 库最常见的文件的列表,在Src/Inc 下面还有Legacy 文件夹,用于特殊外设的补充说明。我们的教程中用到的比较少,这里不展开描述。
不止文件命名有一定规则,stm32f1xx_hal_ppp (c/h)中的函数和变量命名也严格按照命名规
则,如表7.3.2.2 所示的命名规则在大部分情况下都是正确的:
在这里插入图片描述
对于HAL 的API 函数,常见的有以下几种:
⚫ 初始化/反初始化函数:HAL_PPP_Init(), HAL_PPP_DeInit()
⚫ 外设读写函数:HAL_PPP_Read(),HAL_PPP_Write(),HAL_PPP_Transmit(), HAL_PPP_Receive()
⚫ 控制函数:HAL_PPP_Set (),HAL_PPP_Get ().
⚫ 状态和错误:HAL_PPP_GetState (), HAL_PPP_GetError ().
HAL 库封装的很多函数都是通过定义好的结构体将参数一次性传给所需函数,参数也有一定的规律,主要有以下三种:
➢ 配置和初始化用的结构体:
一般为PPP_InitTypeDef 或PPP_ConfTypeDef 的结构体类型,根据外设的寄存器设计成易于理解和记忆的结构体成员。
➢ 特殊处理的结构体
专为不同外设而设置的,带有“Process”的字样,实现一些特异化的中间处理操作等。
➢ 外设句柄结构体
HAL 驱动的重要参数,可以同时定义多个句柄结构以支持多外设多模式。HAL 驱动的操作结果也可以通过这个句柄获得。有些HAL 驱动的头文件中还定义了一些跟这个句柄相关的一些外设操作。如用外设结构体句柄与HAL 定义的一些宏操作配合,即可实现一些常用的寄存器位操作。比较常见的HAL 库寄存器操作如表7.3.2.3 所示:
在这里插入图片描述
但对于SYSTICK/NVIC/RCC/FLASH/GPIO 这些内核外设或共享资源,不使用PPP_HandleTypedef 这类外设句柄进行控制,如The HAL_GPIO_Init()只需要初始化的GPIO 编号和具体的初始化参数。

HAL_StatusTypeDef HAL_GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef *Init)
{
	/*GPIO 初始化程序……*/
}

最后要分享的是HAL 库的回调函数,这部分允许用户重定义,并在其中实现用户自定义的功能,也是我们使用HAL 库的最常用的接口之一:
在这里插入图片描述

至此,我们大概对HAL 库驱动文件的一些通用格式和命名规则有了初步印象,记住这些规则可以帮助我们快速对HAL 库的驱动进行归类和判定这些驱动函数的用法。

ST 官方给我们提供了快速查找API 函数的帮助文档。我们如果解压了stm32cubef1 的固件包后,在路径“STM32Cube_FW_F1_V1.8.3\Drivers\STM32F1xx_HAL_Driver”下可以找到几个chm 格式的文档,根据我们开发板主控芯片STMF103ZE 我们没有找到直接可用的,但可以查看型号接近的:STM32F103xG_User_Manual.chm(因为G 系列比E 系列引脚功能更多,只是查看API 函数不响应使用的)。双击打开后,可以看到左边目录下有四个主题,我们来查看Modules。

以外设GPIO 为例,讲一下怎么使用这个文档。点击GPIO 外设的主题下的IO operation functions / functions 看看里面的API 函数接口描述,如图7.3.2.1 所示。

在这里插入图片描述

这个文档提供的信息很全,不看源码都可以直接使用它来编写代码,还给我们指示源码位置,非常方便。大家多翻一下其他主题了解一下文档的信息结构,很容易使用。
下面举个例子,比如我们要让PB4 输出高电平。先看函数功能,HAL_GPIO_WritePin 函数就是我们的GPIO 口输出设置函数,如图7.3.2.2 所示。

在这里插入图片描述
函数有三个形参:
第一个形参是GPIO_TypeDef *GPIOx,形参描述说:x 可以是A 到G 之间任何一个,而我们是PB4 引脚,所以第一个形参确认是GPIOB。
第二个形参是uint16_t GPIO_Pin,看形参描述:该参数可以是GPIO_PIN_x,x 可以1 到15,那么我们第二个形参就是GPIO_PIN_4。
第三个形参是GPIO_PinState PinState,看形参描述:该参数可以是枚举里的两个数,一个是GPIO_PIN_RESET:表示该位清零,另一个是GPIO_PIN_SET:表示设置该位,即置1 ,我
们要输出1,所以要置1 该位,那么我们第三个形参就是GPIO_PIN_SET。
最后看函数返回值:None,没有返回值。
所以最后得出我们要调用的函数是:

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_SET);

帮助文档的使用就讲到这。
本节只针对HAL 库作了一个简单的介绍,想要了解更多知识,ST 提供了关于HAL 库LL库的更详细的说明文档,我们已经把它放到光盘资料A 盘《8,STM32 参考资料》中了,大家可以自行查阅《Description of STM32F1 HAL and low-layer drivers.pdf》获取所需知识。

如何使用HAL 库

我们要先知道STM32 芯片的某个外设的性能和工作模式,才能借助HAL 库来帮助我们编程,甚至修改HAL 库来适配我们的开发项目。HAL 库的API 虽多,但是查找和使用有规律可循,只要学会其中一个,其他的外设就是类似的,只是添加自己的特性的API 而已。

学会用HAL 库组织开发工具链

需要按照芯片使用手册建议的步骤去配置芯片。HAL 库驱动提供了芯片的驱动接口,但我们需要强调一个概念是使用HAL 库的开发是对芯片功能的开发,而不是开发这个库,也不是有也这个库能就直接开发。如果我们对芯片的功能不作了解的话,仍然不知道按照怎样的步骤和寻找哪些可用的接口去实现想要实现的功能。ST 提供芯片使用手册《STM32F1xx 参考手册.pdf》告诉我们使用某一外设功能时如何具体地去操作每一个用到的寄存器的细节,后面我们的例程讲解过程也会结合这个手册来分析配置过程。

嵌入式的软件开发流程总遵循以下步骤:组织工具链、编写代码、生成可执行文件、烧录到芯片、芯片根据内部指令执行我们编程生成的可执行代码。

在HAL 库学习前期,建议以模仿和操作体验为基础,通过例程来学习如何配置和驱动外设。下面根据我们后续要学习的工程梳理出来的基于CMSIS 的一个HAL 库应用程序文件结构,帮助读者学习和体会这些文件的组成意义,如下表7.4.1.1 所示。
在这里插入图片描述
在这里插入图片描述
把这些文件组织起来的方法,我们会在后续章节新建工程中介绍,这只提前告诉一下大家组成我们需要的编译工具链大概需要哪些文件。

HAL 库的用户配置文件

stm32f1xx_hal_conf.h 用于裁剪HAL 库和定义一些变量,官方没有直接提供这个文件,但在STM32Cube_FW_F1_V1.8.0\Drivers\STM32F1xx_HAL_Driver\Inc 这个路径下提供了一个模
版文件《stm32f1xx_hal_conf_template.h 》,我们可以直接复制这个文件重命名为
stm32f1xx_hal_conf.h,做一些简单的修改即可,也可以从在官方的例程中直接复制过来。我们
开发板使用的芯片是STM32F103 的E 系列,所以也可以从下面的路径获取这个配置文件:
STM32Cube_FW_F1_V1.8.0\Projects\STM3210E_EVAL\Templates\Inc。
(1)配置外部高速晶振的频率。HSE_VALUE 这个参数表示我们的外部高速晶振的频率。这个参数请务必根据我们板子外部焊接的晶振频率来修改,源码在78 行开始,官方默认是25M。
正点原子STM32F103 开发板外部高速晶振的频率是8MHZ。我们没有在代码的其它地方定义过HSE_VALUE 这个值,所以编译器最终会引用这里的值8MHz 作为外部调整晶振的频率值。
注意事项:使用官方的开发板需要定义USE_STM3210C_EVAL这个宏,我们没有在代码的其它位置中或者编译器的预编译选项中定义过这个宏。

#if !defined (HSE_VALUE)
#if defined(USE_STM3210C_EVAL)
#define HSE_VALUE 25000000U /*!< Value of the External oscillator in Hz */
#else
#define HSE_VALUE 8000000U /*!< Value of the External oscillator in Hz */
#endif
#endif /* HSE_VALUE */

还可以把上面的代码直接精简为一行,效果是一样的:

#define HSE_VALUE 8000000U /*!< Value of the External oscillator in Hz */

(2)还有一个参数就是外部低速晶振频率,这个用于RTC 时钟,这个官方默认是32.768KHZ,我们开发板的低速晶振也是这个频率,所以不用修改,源码在111 行。

#if !defined (LSE_VALUE)
#define LSE_VALUE ((uint32_t)32768) /* 外部低速振荡器的值,单位HZ */
#endif /* LSE_VALUE */

(3)用户配置文件可以用来选择使能何种外设,源码配置在31 行到71 行,代码如下。

/* ########################## Module Selection ############################# */
/**
* @brief This is the list of modules to be used in the HAL driver
*/
#define HAL_MODULE_ENABLED
#define HAL_ADC_MODULE_ENABLED
#define HAL_CEC_MODULE_ENABLED
#define HAL_COMP_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED
...中间省略...
#define HAL_USART_MODULE_ENABLED
#define HAL_WWDG_MODULE_ENABLED
#define HAL_MMC_MODULE_ENABLED

比如要不使用GPIO 的功能,把源码在49 行这个宏注释掉即可,具体如下。

/* #define HAL_GPIO_MODULE_ENABLED */

结合同样在《stm32f1xx_hal_conf.h》中的246 行的代码:

#ifdef HAL_GPIO_MODULE_ENABLED
#include "stm32f1xx_hal_gpio.h"
#endif /* HAL_GPIO_MODULE_ENABLED */

这是一个条件编译符,与#endif 配合使用。这里的要表达的意思是,只要工程中定义了HAL_GPIO_MODULE_ENABLED 这个宏,就会包含stm32f1xx_hal_gpio.h 这个头文件到我们的工程,同时stm32f1xx_hal_gpio.c 中的#ifdef 到#endif 之间的程序(116 行到579 行)就会参与编译,否则不编译。所以只要我们屏蔽了stm32f1xx_hal_conf.h 文件49 行的宏,GPIO的驱动代码就不被编译。也就起到选择使能何种外设的功能,其他外设同理。使用时定义,否则不定义。这样就可以在不修改源码的前提下方便地裁剪HAL 库代码的体积了。
注意第一个宏定义:

#define HAL_MODULE_ENABLED

它决定了《stm32f1xx_hal.c》中的第47~587 行的代码是否能使用,也是根据条件编译来实现的。其中包含HAL_Init()、HAL_Delay()、HAL_GetTick()这些其它驱动函数可能需要引用的函数,所以这个宏也是必须要定义的。
官方的示范例程,就是通过屏蔽外设的宏的方法来选择使能何种外设。表现上就是编译时间会变短,因为屏蔽了不使用的HAL 库驱动,编译时间自然就短了。正点原子的例程选择另外一中方法,就是工程中只保留需要的stm32f1xx_hal_ppp.c,不需要的不添加到工程里,由于找不到源文件且没有引用这些文件,同样编译器不会去编译这些代码。
关于配置文件我们暂时只讲这些,具体其它需要修改的地方,我们在例程讲解中再去说明。
(4)大家看到STM32F1xx_hal_conf.h 文件的127 行。

#define TICK_INT_PRIORITY ((uint32_t)0x0F) /*!< tick interrupt priority */

宏定义TICK_INT_PRIORITY 是滴答定时器的优先级。这个优先级很重要,因为如果其它的外设驱动程序的延时是通过滴答定时器提供的时间基准,来实现延时的话,又由于实现方式是滴答定时器对寄存器进行计数,所以当我们在其它中断服务程序里调用基于此时间基准的延迟函数HAL_Delay,那么假如该中断的优先级高于滴答定时器的优先级,就会导致滴答定时器中断服务函数一直得不到运行,从而程序卡死在这里。所以滴答定时器的中断优先级一定要比这些中断高。
请注意这个时间基准可以是滴答定时器提供,也可以是其他的定时器,默认是用滴答定时器。

(5)断言这个功能我们在使程中不使用。断言这个功能用来判断HAL 函数的形参是否有效,并在参数错误时启用这个断言功能,告诉开发者代码错误的位置,断言功能由用户自己决定。这个功能的使能开关代码是一个宏,在源码的160 行,默认是关闭的,代码如下。

/* #define USE_FULL_ASSERT 1 */

通过宏USE_FULL_ASSERT 来选择功能,在源码375 行到389,代码如下。

#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)0U : \
												assert_failed((uint8_t *)__FILE__, __LINE__))
/* Exported functions ------------------------------------------------------- */
void assert_failed(uint8_t* file, uint32_t line);
#else
#define assert_param(expr) ((void)0U)
#endif /* USE_FULL_ASSERT */

也是通过条件编译符来选择对应的功能。当用户自己需要使用断言功能,怎么做呢?首先需要定义宏USE_FULL_ASSERT 来使能断言功能,即把源码的160 行的注释去掉即可。然后看到源码423 行的assert_failed()这个函数。根据这个宏定义,我们还需要去定义这个函数的功能,可以按以下格式自定义这个函数,出错后使代码停留在这里:

#ifdef USE_FULL_ASSERT
/**
* @brief 当编译提示出错的时候此函数用来报告错误的文件和所在行
* @param file:指向源文件
* line:指向在文件中的行数
* @retval 无
*/
void assert_failed(uint8_t* file, uint32_t line)
{
	while (1)
	{
	}
}
#endif

可以看到这个函数里面没有实现如何功能,就是一个什么不做的死循环,具体功能请根据自己的需求去实现。file 是指向源文件的指针,line 是指向源文件的行数。__FILE__是表示源文件名,__LINE__是表示在源文件中的行数。比如我们可以实现打印出这个错误的两个信息等等,但前提是你已经学会了如何使芯片输出打印信息这些功能。

总的来说断言功能就是,在HAL 库中,如果定义了USE_FULL_ASSERT 这个宏,那么所有的HAL 库函数将会检查函数的形参是否正确。如果错误将会调用assert_failed()这个函数,程序就会停留在这里,用户可以定位到出错的函数。这个功能实际上是在芯片上运行的时候的增加错误提示信息的功能,属于调试功能的一部分,实际我们的编译器就可以帮助定位到参数错误的问题并提示信息。在F103 的工程中我们不使用这个功能。

stm32f1xx_hal.c 文件

这个文件内容比较多,包括HAL 库的初始化、系统滴答、基准电压配置、IO 补偿、低功耗、EXTI 配置等都集合在这个文件里面。下面我们对该文件进行讲解。

  1. HAL_Init()函数
    源码在142 行到167 行,简化函数如下(下面的代码只针对F1 的HAL 固件1.8.3 版本,其它版本可能有差异):
HAL_StatusTypeDef HAL_Init(void)
{
	/* 配置Flash的预取控制器*/
	#if (PREFETCH_ENABLE != 0)
	#if defined(STM32F101x6) || defined(STM32F101xB) || defined(STM32F101xE) || de-fined(STM32F101xG) || \ 		defined(STM32F102x6) || defined(STM32F102xB) || \
	defined(STM32F103x6) || defined(STM32F103xB) || defined(STM32F103xE) || de-fined(STM32F103xG) || \ defined(STM32F105xC) || defined(STM32F107xC)
	/* Prefetch buffer is not available on value line devices */
	__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
	#endif
	#endif /* 使能Flash的预取控制器*/
	/* 配置中断优先级顺序*/
	HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
	/* 使用滴答定时器作为时钟基准,配置1ms滴答(重置后默认的时钟源为HSI)*/
	HAL_InitTick(TICK_INT_PRIORITY);
	/* 初始化其它底层硬件(如果必要)*/
	HAL_MspInit();
	/* 返回函数状态*/
	return HAL_OK;
}

该函数是HAL 库的初始化函数,原则上在程序中必须优先调用,其主要实现如下功能:
1)使能Flash 的预取缓冲器(根据闪存编程手册,打开预取指令可以提高对I-Code 总线的访问效率,且AHB 时钟的预分频系数不为1 时,必须打开预取缓冲器)
2)设置NVIC 优先级分组为4。
3)配置滴答定时器每1ms 产生一个中断。
在这个阶段,系统时钟还没有配置好,因此系统还是默认使用内部高速时钟源HSI 在跑程序。对于F1 来说,HSI 的主频是8MHZ。所以如果用户不配置系统时钟的话,那么系统将会使用HSI 作为系统时钟源。
4)调用HAL_MspInit 函数初始化底层硬件,HAL_MspInit 函数在STM32F1xx_hal.c 文件里面做了弱定义。关于弱定义这个概念,后面会有讲解,现在不理解没关系。正点原子的HAL 库例程是没有使用到这个函数去初始化底层硬件,而是单独调用需要用到的硬件初始化函数。用户可以根据自己的需求选择是否重新定义该函数来初始化自己的硬件。
注意事项:为了方便和兼容性,正点原子的HAL 库例程中的中断优先级分组设置为分组2,即把源码的HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)改为如下代码:

HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);

中断优先级分组为2,也就是2 位抢占优先级,2 位响应优先级,抢占优先级和响应优先级的值的范围均为0-3。
2. HAL_DeInit ()函数
源码在175 行到194 行,简化后函数如下:

HAL_StatusTypeDef HAL_DeInit(void)
{
	/* 重置所有外设*/
	__HAL_RCC_APB1_FORCE_RESET();
	__HAL_RCC_APB1_RELEASE_RESET();
	__HAL_RCC_APB2_FORCE_RESET();
	__HAL_RCC_APB2_RELEASE_RESET();
	/* 对底层硬件初始化*/
	HAL_MspDeInit();
	/* 返回函数状态*/
	return HAL_OK;
}

该函数取消初始化HAL 库的公共部分,并且停止systick,是一个可选的函数。该函数做了一下的事:
1)复位了APB1、APB2 的时钟。
2)调用HAL_MspDeInit 函数,对底层硬件初始化进行复位。HAL_MspDeInit 也在STM32F1xx _hal.c 文件里面做了弱定义,并且与HAL_MspInit 函数是一对存在。HAL_MspInit函数负责对底层硬件初始化,HAL_MspDeInit 函数则是对底层硬件初始化进行复位。这两个函数都是需要用户根据自己的需求去实现功能,也可以不使用。
3. HAL_InitTick ()函数
源码在234 行到255 行,简化函数如下:

__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
	/* 配置滴答定时器1ms产生一次中断*/
	if (HAL_SYSTICK_Config(SystemCoreClock /(1000UL / (uint32_t)uwTickFreq))> 0U)
	{
		return HAL_ERROR;
	}
	/* 配置滴答定时器中断优先级*/
	if (TickPriority < (1UL << __NVIC_PRIO_BITS))
	{
		HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
		uwTickPrio = TickPriority;
	}
	else
	{
		return HAL_ERROR;
	}
	/* 返回函数状态*/
	return HAL_OK;
}

该函数用于初始化滴答定时器的时钟基准,主要功能如下:
1)配置滴答定时器1ms 产生一次中断。
2)配置滴答定时器的中断优先级。
3)该函数是__weak 修饰的函数,如果在关联工程中的其它地方没有定义__weak 后面的函数,这里是HAL_InitTick(),则使用此处的定义,否则就使用其它地方定义好的函数功能。我们可以通过重定义这个函数来选择其它的时钟源(如定时器)作为HAL 库函数的时基或者通过重定义不开启Systick 的功能和中断等。
该函数可以通过HAL_Init()或者HAL_RCC_ClockConfig()重置时钟。在默认情况下,滴答定时器是时间基准的来源。如果其他中断服务函数调用了HAL_Delay(),必须小心,滴答定时器中断必须具有比调用了HAL_Delay()函数的其他中断服务函数的优先级高(数值较低),否则会导致滴答定时器中断服务函数一直得不到执行,从而卡死在这里。
4. 滴答定时器相关的函数
源码在293 行到406 行,相关函数如下:

/* 该函数在滴答定时器时钟中断服务函数中被调用,一般滴答定时器1ms中断一次,
   所以函数每1ms让全局变量uwTick计数值加1 */
__weak void HAL_IncTick(void)
{
        uwTick += (uint32_t)uwTickFreq;
}
/* 获取全局变量uwTick当前计算值*/
__weak uint32_t HAL_GetTick(void)
{
        return uwTick;
}
/* 获取滴答时钟优先级*/
uint32_t HAL_GetTickPrio(void)
{
        return uwTickPrio;
}
/* 设置滴答定时器中断频率*/
HAL_StatusTypeDef HAL_SetTickFreq(HAL_TickFreqTypeDef Freq)
{
        HAL_StatusTypeDef status = HAL_OK;
        HAL_TickFreqTypeDef prevTickFreq;
        assert_param(IS_TICKFREQ(Freq));
        if (uwTickFreq != Freq)
        {
                /* 备份滴答定时器中断频率*/
                prevTickFreq = uwTickFreq;
                /* 更新被HAL_InitTick()调用的全局变量uwTickFreq */
                uwTickFreq = Freq;
                /* 应用新的滴答定时器中断频率*/
                status = HAL_InitTick(uwTickPrio);
                if (status != HAL_OK)
                {
                        /* 恢复之前的滴答定时器中断频率*/
                        uwTickFreq = prevTickFreq;
                }
        }
        return status;
}
/* 获取滴答定时器中断频率*/
HAL_TickFreqTypeDef HAL_GetTickFreq(void)
{
        return uwTickFreq;
}
/*HAL库的延时函数,默认延时单位ms */
__weak void HAL_Delay(uint32_t Delay)
{
        uint32_t tickstart = HAL_GetTick();
        uint32_t wait = Delay;
        /* Add a freq to guarantee minimum wait */
        if (wait < HAL_MAX_DELAY)
        {
                wait += (uint32_t)(uwTickFreq);
        }
        while ((HAL_GetTick() - tickstart) < wait)
        {
        }
}
/* 挂起滴答定时器中断,全局变量uwTick计数停止*/
__weak void HAL_SuspendTick(void)
{
        /* 禁止滴答定时器中断*/
        CLEAR_BIT(SysTick->CTRL, SysTick_CTRL_TICKINT_Msk);
}
/* 恢复滴答定时器中断,恢复全局变量uwTick计数*/
__weak void HAL_ResumeTick(void)
{
        /* 使能滴答定时器中断*/
        SET_BIT(SysTick->CTRL, SysTick_CTRL_TICKINT_Msk);
}

这些函数不是很难,请参照注释理解。注意:如果函数被前缀__weak 定义,则用户可以重新定义该函数。更多的内容可以参考8.1.5 小节。
5. HAL 库版本相关的函数
源码在422 行到484 行,相关函数声明在stm32f1xx_hal.h 中,详看如下:

uint32_t HAL_GetHalVersion(void); /* 获取HAL库驱动程序版本*/
uint32_t HAL_GetREVID(void); /* 获取设备修订标识符*/
uint32_t HAL_GetDEVID(void); /* 获取设备标识符*/
uint32_t HAL_GetUIDw0(void); /* 获取唯一设备标识符的第一个字*/
uint32_t HAL_GetUIDw1(void); /* 获取唯一设备标识符的第二个字*/
uint32_t HAL_GetUIDw2(void); /* 获取唯一设备标识符的第三个字*/

这些函数了解一下就好了,用得不多。

  1. 调试功能相关函数
    源码在490 行到587 行,函数声明如下:
void HAL_DBGMCU_EnableDBGSleepMode(void);
void HAL_DBGMCU_DisableDBGSleepMode(void);
void HAL_DBGMCU_EnableDBGStopMode(void);
void HAL_DBGMCU_DisableDBGStopMode(void);
void HAL_DBGMCU_EnableDBGStandbyMode(void);
void HAL_DBGMCU_DisableDBGStandbyMode(void);

这六个函数用于调试功能,默认调试器在睡眠模式下无法调试代码,开发过程中配合这些函数,可以在不同模式下(睡眠模式、停止模式和待机模式),使能或者失能调试器,当我们使用到时再作详细。

HAL 库中断处理

中断是STM32 开发的一个很重要的概念,这里我们可以简单地理解为:STM32 暂停了当前手中的事并优先去处理更重要的事务。而这些“更重要的事务”是由软件开发人员在软件中定义的。关于STM32 中断的概念,我们会在中断例程的讲解再跟大家详细介绍。
由于HAL 库中断处理的逻辑比较统一,我们将这个处理过程抽象为图7.4.4.1 所表示的业务逻辑:

在这里插入图片描述
结合以前的HAL 库文件介绍章节,以上的流程大概就是:设置外设的控制句柄结构体PPP_HandleType 和初始化PPP_InitType 结构体的参数,然后调用HAL 库对应这个驱动的初始
化HAL_PPP_Init(),由于这个API 中有针对外设初始化细节的接口Hal_PPP_Mspinit(),我们需要重新实现这个函数并完成外设时钟、IO 等细节差异的设置,完成各细节处理后,使用HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ()来使能我们的外设中断;
定义中断处理函数PPP_IRQHandler,并在中断函数中调用HAL_ppp_function_IRQHandler()
来判断和处理中断标记;HAL 库中断处理完成后,根据对应中的调用我们需要自定义的中断回调接口HAL_PPP_ProcessCpltCallback();如串口接收函数HAL_UART_RxCpltCallback(),我们在这个函数中实现我们对串口接收数据想做的处理;
中断响应处理完成后,stm32 芯片继续顺序执行我们定义的主程序功能,按照以上处理的标准流程完成了一次中断响应。

正点原子对HAL 库用法的个性化修改

前面介绍了ST 官方建议的HAL 库的使用方法,这部分我们结合我们实际例程的编写,列出例程中对HAL 库使用上的一些与官方推荐用法的差异,读者们请结合自己的使用习惯,辩证地去看待我们这种修改方式:
1,每个例程的BSP 下都有一个HAL 库,并且代码中对文件的引用尽量使用相对路径,保证每个复制完整的例程,在其他路径下也能编译通过;这种做法增加了HAL 库全部例程的体积;
2,将中断处理函数独立到每个外设中,便于独立驱动;同类型的外设驱动处理函数不使用HAL 回调函数接口处理操作而直接在中断函数中处理判断对应中断;
3,我们把原来的中断分组进行了修改,由抢占式无子优先级改为中断分组2;便于管理同类外设的优先级响应;
4,我们编写的初始化函数或者芯片操作时序上的延时,我们使用到了delay_ms()、delay_us()等函数进行初始化,使用的是Systick 作的精准延时,而HAL 库默认也使用Systick 作延时处理,为解决这种冲突和兼容我们大部分的驱动代码,我们在例程中使用delay.c 中的延时函数取代Hal_Delay();取消原来HAL 库的Systick 延时设置。

HAL 库使用注意事项

本小节根据经验跟大家讲述一些关于HAL 库使用的注意事项,供读者参考。
1,即使我们已经在使用库函数作为开发工具了,我们可以忽略很多芯片的硬件外设使用上的细节,但当发生问题时,我们仍需要回归到芯片使用手册查看当前操作是否违规或缺漏;
2,使用HAL 库和其它第三方的库开发类似,把我们需要编写的软件和第三方的库分开成相互独立的文件,开发过程中我们尽量不去修改第三方的软件源码,需要修改的部分尽量在自己的代码中实现;这样一旦我们需要更新第三方库时,我们原来编写的功能也能很快地匹配新的库去执行功能;
3,即使HAL 库目前较以前已经相对更完善了,但它仍无法覆盖我们要想实现的所有细节功能,甚至可能存在错误,我们要有怀疑精神,辩证地去使用好这个工具;如我们在PWM一节编码时发现HAL 库中有个宏定义TIM_RESET_CAPTUREPOLARITY 括号不匹配导致编译报错,这时我们不得不修改一下HAL 库的源码了。
4,注意HAL 库的执行效率。由于HAL 库的驱动对相同外设大多是可重入的,在执行HAL 驱动的API 函数的效率没有直接寄存器操作来得高,如果在对时序要求比较严苛的代码,建议使用简洁的寄存器操作代替;
5,我们在例程中使用delay.c 中的延时函数取代Hal_Delay();取消原来HAL 库的Systick 延时设置;但这会有一个问题:原来HAL 库的超时处理机制不再适用,所以对于设置了超时的函数,可能会导致停留在这个函数的处理中,无法按正常的超时退出;
6,我们建议如无致命BUG 出现,尽量使用开发学习时已经测试稳定的HAL 库来继续进行开发,不必要频繁更新HAL 库,因为更新HAL 库后可能会导致原来能正常运行的代码,在更新HAL 库之后反面反面无法运行的情况,有些函数操作的结构方式变化了等等,这里只是笔者的建议,大家自己的实际情况权衡。

新建HAL 版本MDK 工程

在前面的章节我们介绍了STM32F1xx 官方固件包的一些知识,本章我们将重点讲解新建HAL 库版本的MDK 工程的详细步骤。我们把本章新建好的工程放在光盘里,路径:4,程序源码\2,标准例程-HAL 库版本\实验0 基础入门实验\实验0-3,新建工程实验-HAL 库版本,大家在学习新建工程过程中间遇到一些问题,可以直接打开这个工程,然后对比学习。

新建HAL 库版本MDK 工程

本节我们将教大家如何新建一个STM32F103 的MDK5 工程。为了方便大家参考,我们将本节最终新建好的工程模板存放在“A 盘:4、程序源码\2,标准例程-HAL 库版本\实验0 基础入门实验\实验0-3,新建工程实验-HAL 库版本”,如遇新建工程问题,请打开该实验对比。
在新建工程之前,首先我们要做如下准备:

  1. STM32Cube 官方固件包:我们使用的固件包版本是STM32Cube_FW_F1_V1.8.3,固件包路径:A 盘→8,STM32 参考资料→1,STM32CubeF1 固件包。
  2. 开发环境搭建:参考本书第三章相关内容。

新建工程文件夹

新建工程文件夹分为2 个步骤:1,新建工程文件夹;2,拷贝工程相关文件。

  1. 新建工程文件夹
    首先我们要在电脑某个路径下新建一个文件作为工程的根目录文件,后续的工程文件都将在这个文件夹里建立,我们把这个文件夹重命名为“实验0-3,新建工程实验-HAL 库版本”。如图8.1.1.1 所示。
    在这里插入图片描述
    为了让工程的文件目录结构更加清晰,我们会在工程的根目录文件夹下建立以下几个文件夹,文件夹名称及其作用如表8.1.1.1:

在这里插入图片描述

新建完成以后,最后得到我们的工程根目录文件夹如图8.1.1.2 所示。
在这里插入图片描述
另外我们的工程根文件目录下还有一个名为keilkill.bat 的可执行文件,双击便可执行。其作用是删除编译器编译后的中间文件,减少工程占用的硬盘空间,方便我们打包。还有一个名为readme 的记事本文件,其作用是介绍本实验的各种信息。
工程根目录的文件夹新建好后,我们需要拷贝一些工程相关文件过来(主要是在Drivers文件夹里面),以便等下的新建工程需要。
2. 拷贝工程相关文件
接下来,我们按图8.1.1.2 的根目录文件夹顺序介绍每个文件夹及其需要拷贝的文件。
Drivers 文件夹
该文件夹用于存放与硬件相关的驱动层文件,一般包括如表8.1.1.2 所示的三个文件夹:
在这里插入图片描述
BSP 文件夹,用于存放正点原子提供的板级支持包驱动代码(原HARDWARE 文件夹下),如:LED、蜂鸣器、按键等。本章我们暂时用不到该文件夹,不过可以先建好备用。

SYSTEM 文件夹,用于存放正点原子提供的系统级核心驱动代码,sys.c/h,usart.c/h,delay.c/h,方便大家快速搭建自己的工程。该文件同样可以从“A 盘→4,程序源码→2,标准例程-HAL 库版本”文件夹里面的任何一个实验的Drivers 文件夹里面拷贝过来。
CMSIS 文件夹,用于存放CMSIS 底层代码(ARM 和ST 提供),如:启动文件(.s 文件)、stm32f1xx.h 等各种头文件。该文件夹我们可以直接从STM32CubeF1 固件包(路径:A 盘→8,STM32 参考资料→1,STM32CubeF1 固件包)里面拷贝,CMSIS 的文件夹路径在“STM32CubeF1
固件包→Drivers”。由于这个文件夹原来设计是用于匹配全部F1 系列的芯片的,导致非常大,部分文件对我们的例程来说不会使用到,而且浪费磁盘的存储空间,所以我们会对这个文件夹进行精简:打开目录“CMSIS\Device\ ST\STM32F1xx”,其中的Include 文件夹里都是芯片的头文件我们只留下如图8.1.1.3 这三个头文件,其他删除。
在这里插入图片描述
Source 文件夹下的Templates 文件夹留下如图8.1.1.4 的内容。
在这里插入图片描述
arm 文件夹存放的是启动文件,我们只需要startup_stm32f103xe.s,其他全部删除。如图8.1.1.5 所示。
在这里插入图片描述
最后就是CMSIS 文件夹下的Include 文件夹,里面都是内核的头文件,我们只需要如图8.1.1.6 的内容。
在这里插入图片描述
到这里CMSIS 文件夹就处理完成了。精简后的CMSIS 文件夹大家也可以在“A 盘→4,程序源码→2,标准例程-HAL 版本”文件夹里面的任何一个实验的Drivers 文件夹里面拷贝过来。
STM32F1xx_HAL_Driver 文件夹,用于存放ST 提供的F1xx HAL 库驱动代码。该文件夹我们可以直接从STM32CubeF1 固件包里面拷贝。直接拷贝“STM32CubeF1 固件包→Drivers”路径下的“STM32F1xx_HAL_Driver”文件夹到我们工程的Drivers 下。该文件夹目录最终如图8.1.1.7 的内容。
在这里插入图片描述
到这里,我们就完成了把官方固件包中必要的驱动文件添加到我们工程文件中。
最终我们新建的Drivers 文件夹目录下的文件构成如图8.1.1.8 所示。
在这里插入图片描述
关于工程根目录下的Drivers 文件操作到这里就完成了。在此过程遇到问题的话,请大家多参考我们提供的“实验0-3,新建工程实验-HAL 库版本工程”,一步步操作。

Middlewares 文件夹
Middlewares 文件夹用于存放正点原子提供的中间层组件文件和第三方中间层文件,比如:USMART、MALLOC、TEXT、FATFS、USB、LWIP、各种OS、各种GUI 等等。我们新建工程实验暂时用不到,留空就行,后面的实验将会陆续添加各种文件。

Output 文件夹
Output 文件夹用于存放编译器编译工程输出的中间文件,比如:.hex、.bin、.o 文件等。这里不需要操作,后面只需要在MDK 里面设置该文件夹为编译输出文件的存放文件夹就行。

Projects 文件夹
Projects 文件夹用于存放MDK 工程,因为我们的工程是基于ARM,所以我们在Projects文件夹里面新建一个命名为MDK-ARM 的文件夹,用于存放MDK 的工程文件,如图8.1.1.9所示。
在这里插入图片描述
User 文件夹
User 文件夹用于存放HAL 库用户配置文件、main.c、中断处理文件,以及配置文件stm32f1xx_hal_conf.h。
我们首先从官方固件包里面直接拷贝官方的模板工程下的HAL 库用户配置文件和中断处理文件到我们的User 文件夹里。官方的模板工程路径:STM32Cube_FW_F1_V1.8.0\Projects\STM3210E_EVAL\Templates,打开Template_Project 文件夹,如图8.1.1.10 所示。
在这里插入图片描述
我们需要的文件就在Inc 和Src 文件夹里面,在这两个文件夹里面找到:stm32f1xx_it.c、stm32f1xx_it.h、stm32f1xx_hal_conf.h 这三个文件,并且拷贝到我们的User 文件夹下。
main.c 文件我们也是放在User 文件夹下,后面在MDK 里面教大家新建.c 文件并保存。
User 文件夹最终构成图如图8.1.1.11 所示。
在这里插入图片描述

新建一个工程框架

打开Keil uVision5,点击菜单Project ->New Uvision Project,如图8.1.2.1 所示。
在这里插入图片描述
然后弹出工程命名和保存的操作窗口,工程文件保存路径为:实验0-2,新建工程实验-HAL库版本\Projects\MDK-ARM,工程名字我们取:atk_f103,最后点击保存即可。具体操作窗口如图8.1.2.2 所示。

在这里插入图片描述
接下来会弹出一个选择Device 的界面,就是选择我们的芯片设备型号,大家根据自己使用的芯片型号依次选择即可。STM32F103 战舰开发板的芯片型号是:STM32F103ZET6,所以我们选择:STMicroelectronics→STM32F1 Series→STM32F103→STM32F103ZE(如果使用的是其他芯片,选择相应的型号就可以了),如图8.1.2.3 所示。
在这里插入图片描述
特别注意:一定要安装对应的器件支持包(即pack 包)才会显示这些内容哦,如果没得选择,请关闭MDK,然后安装光盘:6,软件资料\1,软件\MDK5\ Keil.STM32F1xx_DFP.2.3.0 这个安装包后重试。
点击OK 后,弹出Manage Run-Time Environment 对话框,如图8.1.2.4 所示:
在这里插入图片描述
在这个界面,我们可以添加自己需要的组件,从而方便构建开发环境,不过这里我们不需要。我们直接点击Cancel 即可。这样就得到了我们的初步工程,如图8.1.2.5 所示。
在这里插入图片描述
这只是一个工程的框架,我们还需要把自己需要用到的文件添加到工程里面。虽然前面,我们在工程文件夹里放了很多文件,但是它们并没有关联到工程里面。
我们看看初步工程建立好后,MDK-ARM 文件夹的内容,如图8.1.2.6 所示。
在这里插入图片描述

这里我们说明一下,atk_f103.uvprojx 是工程文件,非常关键,不能轻易删除,MDK5.31 生成的工程文件是以.uvprojx 为后缀。DebugConfig,Listings 和Objects 三个文件夹是MDK 自动生成的文件夹。其中DebugConfig 文件夹用于存储一些调试配置文件,Listings 和Objects 文件夹用来存储MDK 编译过程的一些中间文件。这里,我们把Listings 和Objects 文件夹删除,我们后面会把编译中间文件存放到Output 文件夹。当然,我们不删除这两个文件夹也没有关系,只是我们不用它而已。

至此,我们还只是建了一个框架,还有好几个步骤要做,比如添加文件、魔术棒设置、编写main.c 等。

添加文件

本节将分5 个步骤:1,设置工程名和分组;2,添加启动文件;3,添加User 源码;4,添加SYSTEM 源码;5,添加STM32F1xx_HAL_Driver 源码。

  1. 设置工程名和分组名
    在Project→Target 上右键,选择Manage Project Items…(方法一)或在菜单栏点击品字形红绿白图标(方法二)进入工程管理界面,如下图8.1.3.1 所示:

在这里插入图片描述

在工程管理界面,我们可以执行设置工程名字(Project Targets)、分组名字(Groups)以及添加每个分组的文件(Files)等操作。我们设置工程名字为:Template,并设置五个分组:
Startup(存放启动文件)、User(存放main.c 等用户代码)、Drivers/SYSTEM(存放系统级驱动代码)、Drivers/STM32F1xx_HAL_Driver(存放ST 提供的HAL 库驱动代码)、Readme(存放工程说明文件),如图8.1.3.2 所示:

在这里插入图片描述
设置好之后,我们点击OK,回到MDK 主界面,可以看到我们设置的工程名和分组名如图8.1.3.3 所示。
在这里插入图片描述
这里我们只是新建了一个简单的工程,并没有添加BSP、Middlewares 等分组,后面随着工程复杂程度的增加,我们需要一步步添加对应的分组。

注意:为了让工程结构清晰,我们会尽量让MDK 的工程分组和我们前面新建的工程文件夹对应起来,由于MDK 分组不支持多级目录,因此我们将路径也带入分组命名里面,以便区分。如:User 分组对应User 文件夹里面的源码,Drivers/SYSTEM 分组,对应Drivers/SYSTEM文件夹里面的源码,Drivers/STM32F1xx_HAL_Driver 分组对应Drivers/STM32F1xx_HAL_Driver文件夹里面的源码等。
2. 添加启动文件
启动文件(.s 文件)包含STM32 的启动代码,其主要作用包括:1、堆栈(SP)的初始化;
2、初始化程序计数器(PC);3、设置向量表异常事件的入口地址;4、调用main 函数等,是每个工程必不可少的一个文件,我们在本书第九章会有详细介绍。
该文件由ST 官方提供,对于STM32F103 来说有4 个启动文件可选,如表8.1.3.1 所示:
在这里插入图片描述
启动文件存放的位置在前面也有所说明,因为我们开发板使用的是STM32F103ZET6,对应的启动文件为:startup_stm32f103xe.s。
关于启动文件的说明,我们就介绍这么多,接下来我们看如何添加启动文件到工程里面。我们有两种方法给MDK 的分组添加文件:1,双击Project 下的分组名添加。2,进入工程管理界面添加。
这里我们使用方法1 添加(路径:实验0-3,新建工程实验-HAL 库版本\Drivers\CMSIS
Device\ST\STM32F1xx\Source\Templates\arm),如图8.1.3.4 所示:
在这里插入图片描述

上图中,我们也可以点击Add 按钮进行文件添加。添加完后,点击Close,完成启动文件添加,得到工程分组如图8.1.3.5 所示:

在这里插入图片描述
3. 添加User 源码
这里我们在工程管理界面(方法2)进行User 源码添加。点击:按钮,进入工程管理界面,选中User 分组,然后点击:Add Files,进入文件添加对话框,依次添加stm32f1xx_it.c和system_stm32f1xx.c 到该分组下,如图8.1.3.6 所示:
在这里插入图片描述

注意:这些源码都是在第8.1.1 小节的第二步拷贝过来的,如果之前没拷贝,是找不到这些源码的。添加完成后,如图8.1.3.7 所示:
在这里插入图片描述
4. 添加SYSTEM 源码
同样的,我们也是在工程管理界面进行SYSTEM 源码添加。点击:按钮,进入工程管理界面,选中Drivers/SYSTEM 分组,然后点击:Add Files,进入文件添加对话框,依次添加delay.c、sys.c 和usart.c 到该分组下,如图8.1.3.8 所示:
在这里插入图片描述
添加完成后,如图8.1.3.9 所示:
在这里插入图片描述
5. 添加STM32F1xx_HAL_Driver 源码
在这里插入图片描述
该分组下,如图8.1.3.10 所示:

在这里插入图片描述
添加完成后,如图8.1.3.11 所示:
在这里插入图片描述
可以看到分组中有些.c 文件有个小钥匙的符号,这是因为官方的固件包的文件设置了只读权限,我们取消只读权限就好了,方法如图8.1.3.12 所示。
在这里插入图片描述

魔术棒设置

为避免编写代码和编译报错,我们需要通过魔术棒对MDK 工程进行相关设置。在MDK主界面,点击:(魔术棒图标,即Options for Target 按钮),进入工程设置对话框,我们将进行如下几个选项卡的设置。

  1. 设置Target 选项卡
    在魔术棒→Target 选项卡里面,我们进行如图8.1.4.1 所示设置:
    在这里插入图片描述
    上图中,我们设置芯片所使用的外部晶振频率为8Mhz,选择ARM Compiler 版本为:Use default compiler version 5(即AC5 编译器)。
    这里我们说明一下AC5 和AC6 编译的差异,如表8.1.4.1 所示:
    在这里插入图片描述
    由于AC5 对中文支持比较好,且兼容性相对好一点,为了避免不必要的麻烦,我们推荐大家使用AC5 编译器。为了让大家自由选择,我们正点原子的源码,也是支持AC6 编译器的,不过在选项卡设置上稍有差异,具体差异如表8.1.4.2 所示:
    在这里插入图片描述
  2. 设置Output 选项卡
    在魔术棒→Output 选项卡里面,进行如图8.1.4.2 所示设置:
    在这里插入图片描述

注意,我们勾选:Browse Information,用于输出浏览信息,这样就可以使用go to definition查看函数/变量的定义,对我们后续调试代码比较有帮助,如果不需要调试代码,则可以去掉这个勾选,以提高编译速度。

  1. 设置Listing 选项卡
    在魔术棒→Listing 选项卡里面,进行如图8.1.4.3 所示设置:
    在这里插入图片描述
    经过Output 和Listing 这两步设置,原来存储在Objects 和Listings 文件夹的内容(中间文件)就都改为输出到Output 文件夹了。
  2. 设置C/C++选项卡
    在魔术棒→C/C++选项卡里面,进行如图8.1.4.4 所示设置:
    在这里插入图片描述
    在②处设置了全局宏定义:USE_HAL_DRIVER 和STM32F103xE,他们之间是用英文逗号隔开的。添加全局宏定义标识符,在工程中任何地方都可见,在stm32f1xx.h 里面会用到该宏定义。
    在③处设置了优化等级为-O0,可以得到最好的调试效果,当然为了提高优化效果提升性能并降低代码量,可以设置-O1~-O3,数字越大效果越明显,不过也越容易出问题。注意:当使用AC6 编译器的时候,这里推荐默认使用-O1 优化。
    在④处勾选C99 模式,即使用C99 C 语言标准。
    在⑤处,我们可以进行头文件包含路径设置,点击此按钮,进行如图8.1.4.5 所示设置:
    在这里插入图片描述
    上图中我们设置了5 个头文件包含路径,其中4 个在Drivers 文件夹下,一个在User 文件夹下。为避免频繁设置头文件包含路径,正点原子最新源码的include 全部使用相对路径,也就是我们只需要在头文件包含路径里面指定一个文件夹,那么该文件夹下的其他文件夹里面的源码,如果全部是使用相对路径,则无需再设置头文件包含路径了,直接在include 里面就指明了头文件所在。
    关于相对路径,这里大家记住3 点:
    1,默认路径就是指MDK 工程所在的路径,即.uvprojx 文件所在路径(文件夹)
    2,“./”表示当前目录(相对当前路径,也可以写做“.\”)
    3,“…/”表示当前目录的上一层目录(也可以写做“…\”)
    举例来说,上图中:…\Drivers\CMSIS\Device\ST\STM32F1xx\Include,前面两个“…\”,表示Drivers 文件夹在当前MDK 工程所在文件夹(MDK-ARM)的上2 级目录下,具体解释如图8.1.4.6 所示:
    在这里插入图片描述
    上图表示根据头文件包含路径:…\Drivers\CMSIS\Device\ST\STM32F1xx\Include,编译器
    可以找到⑥处所包含的这些头文件,即代码里面可以直接include 这些头文件使用。
    再举个例子,在完成如图8.1.4.5 所示的头文件包含路径设置以后,我们在代码里面编写:
#include "./SYSTEM/sys/sys.h"

即表示当前头文件包含路径所指示的4 个文件夹里面,肯定有某一个文件夹包含了:
SYSTEM/sys/sys.h 的路径,实际上就是在Drivers 文件夹下面,两者结合起来就相当于:

#include "../../Drivers/SYSTEM/sys/sys.h"

这就是相对路径。它既可以减少头文件包含路径设置(即减少MDK 配置步骤,免去频繁设置头文件包含路径的麻烦),同时又可以很方便的知道头文件具体在那个文件夹,因此我们推荐在编写代码的时候使用相对路径。
关于相对路径,我们就介绍这么多,大家搞不明白的可以在网上搜索相关资料学习,也可以在后面的学习,分析我们其他源码,慢慢体会,总之不难,但是好用。
最后,我们如果使用AC6 编译器,则在图6.1.4.4 的Misc Controls 处需要设置:-Wno-invalid-source-encoding,避免中文编码报错,如果使用AC5 编译器,则不需要该设置!!

  1. 设置Debug 选项卡
    在魔术棒→Debug 选项卡里面,进行如图8.1.4.7 所示设置:

在这里插入图片描述
图中,我们选择使用:CMSIS-DAP 仿真器,使用SW 模式,并设置最大时钟频率为10M hz,以得到最高下载速度。当我们将仿真器和开发板连接好,并给开发板供电以后,仿真器就会找到开发板芯片,并在SW Device 窗口显示芯片的IDCODE、Device Name 等信息(图中⑤处),当无法找到时,请检查供电和仿真器连接状况。

  1. 设置Utilities 选项卡
    在魔术棒→Utilities 选项卡里面,进行如图6.1.4.8 所示设置:
    在这里插入图片描述
    图中⑥处下载算法,是MDK 默认添加的,针对STM32F10x 大容量系列产品(FLASH 容量在256KB~512KB 之间)。一般我们用这个即可。如果⑥处没有下载算法,则点击⑦处按钮,执行添加一下下载算法即可(名字和⑥处的算法名字一样)。

添加main.c,并编写代码

在MDK 主界面,点击:,新建一个main.c 文件,并保存在User 文件夹下。然后双击User 分组,弹出添加文件的对话框,将User 文件夹下的main.c 文件添加到User 分组下。得到如图8.1.5.1 所示的界面:
在这里插入图片描述

至此,我们就可以开始编写我们自己的代码了。我们在main.c 文件里面输入如下代码:

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
void led_init(void); /* LED初始化函数声明*/
int main(void)
{
        HAL_Init(); /* 初始化HAL库*/
        sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
        delay_init(72); /* 延时初始化*/
        led_init(); /* LED初始化*/
        while(1)
        {
                HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET); /* PB5置1 */
                HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET); /* PE5置0 */
                delay_ms(500);
                HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET); /* PB5置1 */
                HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET); /* PE5置0 */
                delay_ms(500);
        }
}
/**
 * @brief 初始化LED相关IO口, 并使能时钟
 * @param 无
 * @retval 无
 */
void led_init(void)
{
        GPIO_InitTypeDef gpio_initstruct;
        __HAL_RCC_GPIOB_CLK_ENABLE(); /* IO口PB时钟使能*/
        __HAL_RCC_GPIOE_CLK_ENABLE(); /* IO口PE时钟使能*/
        gpio_initstruct.Pin = GPIO_PIN_5; /* LED0引脚*/
        gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出*/
        gpio_initstruct.Pull = GPIO_PULLUP; /* 上拉*/
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速*/
        HAL_GPIO_Init(GPIOB, &gpio_initstruct); /* 初始化LED0引脚*/
        gpio_initstruct.Pin = GPIO_PIN_5; /* LED1引脚*/
        HAL_GPIO_Init(GPIOE, &gpio_initstruct); /* 初始化LED1引脚*/
}

此部分代码,在A 盘→4,程序源码→1,标准例程-HAL 库版本→ 实验0 基础入门实验→实验0-3,新建工程实验-HAL 库版本→User→main.c 里面有,大家可以自己输入,也可以直接拷贝。强烈建议自己输入,以加深对程序的理解和印象!!
注意,这里的include 就是使用的相对路径,关于相对路径,请参考前面C/C++选项卡设置章节进行学习。
编写完main.c 以后,我们点击:(Rebuild)按钮,编译整个工程,编译结果如图8.1.5.2。
在这里插入图片描述
我们在Build Output 下找到第一个错误,双击这个错误信息定位到错误发生的代码位置。
如图这个错误说找不到main.h,因为我们也不需要用到main.h,双击这个错误会弹出下面的STM32F1xx_it.c 文件对应包含main.h 的语句。我们只需要把它删除,然后重新编译。
再次编译,发现还有一处警告,这里是HAL_IncTick 函数没有声明,如图8.1.5.3 所示。
在这里插入图片描述
因为这个函数是在STM32F1xx_hal.c 定义了,并且在STM32F1xx_hal.h 声明了。我们把STM32F1xx_hal.h 包含进来即可。这里还有一个原因是整个工程没有包含STM32F1xx_hal.h 的
语句,我们需要用到它,所以在这里把它包含进来。官方的main.h 是有包含这个头文件的。我们不用main.h 文件,我们在STM32F1xx_it.c 文件刚才删除包含main.h 的语句的位置,编写包含STM32F1xx_hal.h 语句,如图8.1.5.3 所示。
在这里插入图片描述
再进行编译就会发现0 错误0 警告,结果如图8.1.5.4 所示:
在这里插入图片描述
编译结果提示:代码总大小(Porgram Size)为:FLASH 占用5780 字节(Code + RO + RW),
SRAM 占用1928 字节(RW + ZI);并成功创建了Hex 文件(可执行文件,放在Output 目录下)。
另外,我们在Readme 分组下还没有添加任何文件,由于只是添加一个说明性质的文件(.txt),
并不是工程必备文件,因此这里我们就不添加了,开发板光盘的源码我们是有添加的,大家可
以去参考一下。

至此,新建寄存器版本MDK 工程完成。

下载验证

有两种方法可以给STM32F103 芯片下载代码:1,使用串口下载;2,使用仿真器下载。这两种下载方法,我们在本书的4.2 和4.3 节给大家有做过详细介绍。这里我们以仿真器下载为例,在MDK 主界面,点击:(下载按钮,也可以按键盘快捷键:F8),就可以将代码下载到开发板,如图8.2.1 所示:
在这里插入图片描述
上图提示:Application running…,则表示代码下载成功,且开始运行。可以看到LED0 和LED1 交叉闪烁。

STM32CubeMX 简介

STM32CubeMX 是由ST 公司开发的图形化代码自动生成工具,能够快速生成初始化代码,如配置GPIO,时钟树,中间件等,使用户专注于业务代码的开发。现在ST 主推HAL 库代码,经典的标准外设库已经停止维护了,新产品也只提供HAL 库的代码,因此,我们学习HAL 库是更加有优势的,由于HAL 库具有低耦合、通用、抽象了硬件层,使得开发者无需太过关注硬件驱动的实现,使得开发更加的简单快速,更容易维护,因此被越来越多的产品所使用。

STM32CubeMX 的作用

STM32CubeMX 具有如下特性:
①直观的选择MCU 型号,可指定系列、封装、外设数量等条件;
②微控制器图形化配置;
③自动处理引脚冲突;
④动态设置时钟树,生成系统时钟配置代码;
⑤可以动态设置外围和中间件模式和初始化;
⑥功耗预测;
⑦C 代码工程生成器覆盖了STM32 微控制器初始化编译软件,如IAR,KEIL,GCC;
⑧可以独立使用或者作为Eclipse 插件使用;
⑨可作为ST 的固件包、芯片手册等的下载引擎;

对于STM32CubeMX 和STM32Cube 的关系这里我们还需要特别说明一下,STM32Cube 包含STM32CubeMX 图形工具和STM32Cube 库两个部分,使用STM32CubeMX 配置生成的代码,是基于STM32Cube 库的。也就是说,我们使用STM32CubeMX 配置出来的初始化代码,和STM32Cube 库兼容,例如硬件抽象层代码就是使用的STM32 的HAL 库。不同的STM32 系列芯片,会有不同的STM32Cube 库支持,而STM32CubeMX 图形工具只有一种。所以我们配置不同的STM32 系列芯片,选择不同的STM32Cube 库即可。
在这里插入图片描述

图10.1.1 STM32CubeMX 和STM32Cube 库的关系

当然,自动生成的驱动代码我们不去仔细专研其原理的话,对学习的提升很有限,而且在出现BUG 的时候难以快速定位解决,因此我们也要了解其背后的原理。

安装STM32CubeMX

STM32CubeMX 运行环境搭建包含两个部分。首先是Java 运行环境安装,其次是STM32CubeMX 软件安装。

安装JAVA 环境

对于Java 运行环境,大家可以到Java 官网www.java.com 下载最新的Java 软件,也可以直接从我们光盘复制安装包,目录为:A 盘→6,软件资料→1,软件→3、STM32CubeMX→Java安装包,Java 安装包文件下有x64 和x86 两个文件夹,分别是64 位和32 位的电脑的安装包,大家根据自己电脑的位数选择即可。比如64 位电脑选择x64 文件夹的jre-8u301-windows-x64.exe安装包,并根据提示安装即可。安装完成之后提示界面如下图10.2.1.1 所示。
在这里插入图片描述
安装完Java 运行环境之后,为了检测是否正常安装,我们可以打开Windows 的命令输入框,输入:java –version 命令,如果显示Java 版本信息,则安装成功。提示信息如下图10.2.1.2:
在这里插入图片描述

安装STM32CubeMX

在安装了Java 运行环境之后,接下来我们安装STM32CubeMX 图形化工具。该软件可以直接从光盘复制,目录为:A 盘→6,软件资料→1,软件→STM32CubeMX,也可以直接从ST官方下载,下载地址为:https://www.st.com/en/development-tools/stm32cubemx.html。
接下来我们直接双击SetupSTM32CubeMX-6.3.0.exe,安装步骤如下。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用STM32CubeMX 新建工程

CubeMX 建立的工程结构和本书介绍的代码风格有所差异,限于篇幅,本部分只介绍如何使用CubeMX 生成MDK 工程的方法。

打开STM32CubeMX

双击如图10.3.1.1 所示的STM32CubeMX 桌面快捷方式图标,打开后CubeMX 主界面如图10.3.1.2 所示。
在这里插入图片描述
在这里插入图片描述

下载和关联的STM32Cube 固件包

新建工程前,我们需要先安装关联与STM32 主芯片对应的STM32Cube 固件包,点击Help->Manage embedded software packages,如图10.3.2.1 所示。
在这里插入图片描述
在弹出的软件包管理界面中,我们可以选择安装驱动包的方式,有以下两种方法:
方式一:从网络下载安装,按照图10.3.2.2 的步骤,在该窗口找到STM32F1 列表选项,因为我们的教程源码使用的固件包是1.8.3 版本的,所以我们勾选1.8.3 版本,等待安装完成即可。
在这里插入图片描述
方法二:不通过网络,直接点击从本地导入。由于直接使用上面的安装包管理器的“From Local”选项导入压缩包有时候会直接报错,比如可以直接导入“stm32cube_fw_f1_v180.zip”固件包,但直接导入“stm32cube_fw_f1_v183.zip”的安装包时CubeMX 软件会报错,所以我们采用以下方法来处理:

① 选择CubeMX 的菜单,Help->Updater Settings,或者在CubeMX 的活动窗口处于前台运行状态时使用它推荐的快捷键“Alt+S”,可以在弹出“更新设置”界面下,找到“Repository Folder”,即CubeMX 的资源仓库,默认是安装在C 盘的一个文件夹下,但由于CubeMX 会把固件库解压到这个文件夹下,在我们使用的STM32 型号多了或者经过一段时间的更新使用后,这个文件夹常常会变得非常大(数十Gb)。所以我们这里把默认的路径设置为“D:\STM32Cube\Repository”,但使用自定义路径时CubeMX 关联的路径一定不能有中文出现,大家根据自己的需要决定是否设置。操作方法如图10.3.2.3 和图10.3.2.4 所示。
在这里插入图片描述
在这里插入图片描述
② 我们复制光盘提供的固件包,把它放到上一步我们设置或者找到的CubeMX 的仓库文件夹。
光盘资料路径为:A 盘→ 8,STM32 参考资料→ 1,STM32CubeF1 固件包,我们这里要使用的是CubeF1 固件的1.8.3 版本。由于CubeF1 的1.8.3 版本是1.8.0 版本的补充包,所以需要把“stm32cube_fw_f1_v180.zip”和“stm32cube_fw_f1_v183.zip”两个固件包都复制到对应的路径下。复制后目录CubeMX 的仓库目录的状况如图10.3.2.4 所示。
在这里插入图片描述
③ 先解压“stm32cube_fw_f1_v180.zip”到当前仓库路径的根路径,注意这里解压缩后的第一级目录就是压缩包下的目录,如图10.3.2.5 所示。
在这里插入图片描述
④ 接着解压“stm32cube_fw_f1_v183.zip”到当前仓库路径的根路径,这里打开压缩包可以发现CubeF1 的1.8.3 版本固件压缩包下的文件夹与1.8.0 的完全相同,我们解压缩并替换文件为1.8.3 中的文件。解压后路径下仍只有一个“STM32Cube_FW_F1_V1.8.0”文件夹。
在这里插入图片描述
⑤关闭CubeMX 软件并重启。重启后通过CubeMX 的安装包管理器,可以发现CubeMX 已经检测到安装好了对应的驱动库。至此离线安装步骤结束。
在这里插入图片描述

新建工程

通过上一步安装固件库后,我们就可以使用STM32CubeMX 配置工程,步骤如下:

  1. 工程初步建立
  2. HSE 和LSE 时钟源设置
  3. 时钟系统(时钟树)配置
  4. GPIO 功能引脚配置
  5. 配置Debug 选项
  6. 生成工程源码
  7. 用户程序
    接下来我们将按照上述步骤,和大家一起使用STM32CubeMX 工具生成一个MDK 工程,如果之前还没有安装过MDK 的,请先按教程关于MDK 安装和介绍部分的内容先安装好软件。
    1 工程初步建立
    方法一:依次点击“File->New Project”即可建新工程。如果之前打开过的话,左侧最近打开的过程一列会有打开的工程列表,直接点击这些工程也可以打开。
    方法二:直接点击ACCESS TO MCU SELECTOR。
    具体操作如图10.3.3.1 所示。
    在这里插入图片描述
    点击新建工程后,可能会弹出如图10.3.3.2 的窗口,提示需要联网下载一些文件,可能等待时间比较长,可以直接选择取消即可。我们可以通过关闭自动更新设置来禁止弹出这个窗口。
    在这里插入图片描述
    之后都可以进入芯片选型界面,如图10.3.3.3 所示。
    在这里插入图片描述
    选择具体的芯片型号,如图10.3.3.4 所示。
    在这里插入图片描述
    鼠标双击选择的芯片型号后,弹出主设计界面,如图10.3.3.5 所示。

在这里插入图片描述
2 HSE 和LSE 时钟源设置
进入工程主设计界面后,首先设置时钟源HSE 和LSE。如图10.3.3.6 所示。
在这里插入图片描述
图10.3.3.6 中的标号④和⑤,我们都选择了Crystal/Ceramic Resonator,表示外部晶振作为它们的时钟源。我们开发板的外部高速晶振和外部低速晶振分别是:8MHZ 和32.768KHZ,所以HSE 时钟频率就是8MHZ,LSE 时钟频率就是32.768KHZ。
选项Master Clock Output 1 用来选择是否使能MCO1 引脚时钟输出。
3 时钟系统(时钟树)配置
点击Clock Configuration 选项卡即可进入时钟系统配置栏,如下图10.3.3.7 所示:

在这里插入图片描述
进入Clock Configuration 配置栏之后可以看到,界面展现一个完整的STM32F1 时钟系统框图。从这个时钟树配置图可以看出,配置的主要是外部晶振大小,分频系数,倍频系数以及选择器。在我们配置的工程中,时钟值会动态更新,如果某个时钟值在配置过程中超过允许值,那么相应的选项框会红色提示。

这里,我们将配置一个以HSE 为时钟源,配置PLL 相关参数,然后系统时钟选择PLLCLK为时钟源,最终配置系统时钟为72MHz 的过程。同时,还配置了AHB,APB1,APB 和Systick的相关分频系数。由于图片比较大,我们把主要的配置部分分两部分来讲解,第一部分是配置系统时钟,第二部分是配置SYSTICK、AHB、APB1 和APB2 的分频系数。首先我们来看看第一部分配置如下图10.3.3.8 所示:
在这里插入图片描述
我们把系统时钟配置分为七个步骤,分别用标号1~5 表示,详细过程为:
①时钟源参数设置:我们选择HSE 为时钟源,所以我们要根据硬件实际的高速晶振频率(这里我们是8MHZ)填写。
②时钟源选择:我们配置选择器选择HSE 即可。
③PLL 倍频系数PLLMUL 配置。倍频系数PLLMUL 我们设置为9。
④系统时钟时钟源选择:PLL,HSI 还是HSE。我们选择PLL,选择器选择PLLCLK 即可。
⑤经过上面配置以后此时SYSCLK=72MHz。
经过上面的5 个步骤,就配置好STM32F1 的系统时钟为72MHz。接下来我们还需要配置AHB、APB1、APB2 和Systick 的分频系数,为STM32 的片上外设或M3 内核设置对应的工作时钟,为后续使用这些硬件功能做好准备。配置如下图10.3.3.9 所示:
在这里插入图片描述
AHB、APB1 和APB2 总线时钟以及Systick 时钟的来源于系统时钟SYSCLK。其中AHB总线时钟HCLK 由SYSCLK 经过AHB 预分频器之后得到,如果我们要设置HCLK 为72MHz(最大为72Mhz),那么我们只需要配置图中标号⑥的地方为1 即可。得到HCLK 之后,接下来我们将在图标号⑦~⑨处同样的方法依次配置Systick、APB1 和APB 分频系数分别为1、2 和1。

注意!systick 固定为72MHz,配置完成之后,那么HCLK=72MHZ,Systic=72MHzPCLK1=36MHz,
PCLK2=72MHz,这和之前例程配置的时钟是主频一样的。

以上方法是手动计算的方法,是为了帮助我们更好地去认识STM32 时钟的配置方法,当然CubeMX 也提供了更简单的方法:在图10.3.3.9 的“HCLK(MHz)”位置,实际上是可以编辑的。

我们直接输入我们要的主频,这里是72Mzh,按回车键,CubeMX 会帮我们提供一种设置主频和其它时钟的建议,选择是后会由软件自动配置好,当然只有启用外部的晶振后才能配置到72Mhz 的时钟,这里大家自己尝试一下就清楚了,我们不展开讲述了。

4 GPIO 功能引脚配置
本小节,我们讲解怎么使用STM32CubeMX 工具配置STM32F1 的GPIO 口。STM32F103战舰开发板的PB5 和PE5 引脚各连接一个LED 灯,我们来学习配置这两个IO 口的相关参数。

这里我们回到STM32CubeMX 的Pinout&Configuration 选项,在搜索栏输入PB5 后回车,可以在引脚图中显示位置,如下图10.3.3.11 所示:
在这里插入图片描述
接下来,我们在图10.3.3.11 引脚图中点击PB5,在弹出的下拉菜单中,选择IO 口的功能为GPIO_Output。操作方法如下图10.3.3.11 所示:
在这里插入图片描述
同样的方法,我们配置PE5 选择功能为GPIO_Oput 即可。设置好即可看到引脚从灰色变成绿色,标识该管脚已经启用。这里我们需要说明一下,如果我们要配置IO 口为外部中断引脚或者其他复用功能,我们选择相应的选项即可。配置完IO 口功能之后,还要配置IO 口的速度,上下拉等参数。这些参数我们通过System Core 下的GPIO 选项进行配置,如图10.3.3.12 所示。
在这里插入图片描述
我们先配置PB5,PE5 和PB5 配置方法一样的。点击图10.3.3.12 的④号框里面的PB5,配置如图10.3.3.13 所示。
在这里插入图片描述
GPIO output level 是IO 的初始值,由于LED 一端接VCC,另一端接GPIO,故要点亮LED灯时,使GPIO 输出低电平即可。为了一开始让LED 灯熄灭,我们设置初始值输出高电平。
GPIO mode 我们已经在视图中配置为推挽输出了,这里不需要修改。
GPIO Pull-up/Pull-down 默认是无上下拉,我们这里用默认配置。
Maximum output speed 输出速度配置,默认是低速,我们设置为高速。
User Label 用户符号,我们可以给PB5 起一个别的名字LED0。
PE5 也是按照这样的方法配置即可。
5 配置Debug 选项
由于CubeMX 默认把Debug 选项关闭了,这样会给我们带来麻烦:用CubeMX 生成的工程编译下载一次后,后续再次下载就会提示错误,因此我们要把Debug 选项打开。这里有多种选择,我们设置成图10.3.3.14 所示的情况即可。
在这里插入图片描述
如果已经不小心关闭了Debug 选项,那么下次下载的时候按住复位键,等到工程提示的时候松开复位键即可,因为STM32 的芯片默认复位上电时的Debug 引脚功能是开启的。
接下来我们学习怎么设置生成一个工程,如图10.3.3.15 所示。选择Project Manager-> Project选项用来配置工程的选项,我们了解一下里面的信息。
Project Name:工程名称,填入工程名称(半角,不能有中文字符)
Project Location:工程保存路径,点击Browse 选择保存的位置(半角,不能有中文字符)
Toolchain Folder Location:工具链文件夹位置,默认即可。
Application Structure:应用的结构,选择Basic(基础),不勾选Do not generate the main(),因为我们要其生成main 函数。
Toolchain/IDE:工具链/集成开发环境,我们使用Keil,因此选择MDK-ARM,Min Version 选择V5.27,这里根据CubeMX 的版本可能会有差异,我们默认使用V5 以上的版本即可。
Linker Settings 链接器设置:
Minimum Heap Size 最小堆大小,默认(大工程需按需调整)。
Minimum Stack Size 最小栈大小,默认(大工程需按需调整)。
MCU and Firmware Package 是MCU 及固件包设置:
MCU Reference:目标MCU 系列名称。
Firmware Package Name and Version:固件包名称及版本。
勾选Use Default Firmware Location,文本框里面的路径就是固件包的存储地址,我们使用默认地址即可。(这里因为我有两个版本的固件包,所以它默认使用最新的,这个关系不大,就用新的)。这样工程生成的设置就设置好了,如图10.3.3.15 所示。
在这里插入图片描述
打开Project Manager-> Code Generator 选项,Generated files 生成文件选项,勾选Generate peripheral initialization as a pair of ‘.c/.h’files per peripheral,勾选这个选项的话将会将每个外设单
独分开成一组.c、.h 文件,使得代码结构更加的清晰,如图10.3.3.16 所示。
在这里插入图片描述
由于CubeMX 默认勾选了复制所有的库,即工程中不使用到的代码也会复制进来,为了节省CubeMX 生成工程的空间,我们勾选生成工程时只复制用到的库(这一步是可选操作,大家根据自己的实际选择),如图图10.3.3.17 所示:
在这里插入图片描述
至此工程最基础配置就已经完成,点击蓝色按钮(SENERATE CODE)就可以生成工程。
在这里插入图片描述
如果我们的CubeMX 工程放置配置路径中没有中文。生成代码后会弹出类似图10.3.3.19 的提示窗口,点击Open Project 就打开MDK 工程(如果是中文路径则会报错,这里暂时不用管,我们先往下继续操作)。
在这里插入图片描述
完整的STM32F1 工程就已经生成完成。生成后的工程目录结构如下图10.3.3.20 所示:
在这里插入图片描述
Drivers 文件夹存放的是HAL 库文件和CMSIS 相关文件。
Inc 文件夹存放的是工程必须的部分头文件。
MDK-ARM 下面存放的是MDK 工程文件。
Src 文件夹下面存放的是工程必须的部分源文件。
Template.ioc 是STM32CubeMX 工程文件,双击该文件就会在STM32CubeMX 中打开。
7 用户程序
在编写用户程序之前,首先我们打开生成的工程模板进行编译,因为我们在之前步骤生成的CubeMX 工程为LED_TEST.ioc,故生成的MDK 工程位置是.\MDK-ARM\LED_TEST.uvprojx,如果大家配置的CubeMX 的工程名和路径名不含中文或中文字符,按上述步骤生成的工程就可以直接编译通过了。
在这里插入图片描述
接下来我们中生成的工程模板的main.c 文件中找到main 函数,这里我们删掉了源码注释,关键源码如下:

int main(void)
{
	HAL_Init();
	SystemClock_config();
	MX_GPIO_Init();
	/* USER CODE BEGIN WHILE */
	while (1)
	{
		/* USER CODE END WHILE */
	}
}

大家需要注意,STM32CubeMX 生成的main.c 文件中,有很多地方有“/* USER CODE BEGIN X /”和“/ USER CODE END X */”格式的注释,我们在这些注释的BEGIN 和END之间编写代码,那么重新生成工程之后,这些代码会保留而不会被覆盖。
我们编写一个跑马灯的用户程序,程序具体如下:

/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	/* USER CODE BEGIN WHILE */
	while (1)
	{
		HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
		HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
		HAL_Delay(500);
		HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
		HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
		HAL_Delay(500);
		/* USER CODE END WHILE */
	}
}

编写好程序后,编译没有任何警告和错误。可以直接下载程序到开发板中,使用DAP 下载,请注意设置MDK 的下载选项,如果不清楚设置的读者可以回看本书第四章的相关知识点。
下载后,可以看到LED0 和LED1 同时按500ms 的频率亮灭,效果与其它版本的新建工程相同。
本小节使用STM32CubeMX 新建的工程模板在我们光盘目录:“4,程序源码\2,标准例程
-HAL 库版本\实验0 基础入门实验\实验0-4,新建工程实验-CubeMX 版本”中有存放,大家在编写用户代码过程中可以参考该工程的main.c 文件。

STM32CubeMX 新建工程使用建议

①使用CubeMX 的环境搭建工程,工程文件夹路径、文件名不要带任何中文及中文字符,
否则会遇到各种报错;
②本书以新建工程-HAL 库版本为基准来展开,不对CubeMX 的使用过多讲解。使用CubeMX 可以帮助我们快速搭建工程,使用户专注于应用开发,但STM32 的开发与硬件密切相关,对STM32 开发来说,抛开底层只专注做应用并不实际,毕竟无法使用一套通用设计来满足不同用户的需求;
③关于新建CubeMX 的工程路径中有中文的情况的解决:
如果我们配置的CubeMX 工程路径里面有中文可能会报以下的错误(再次强调,CubeMX的关联路径、文件名不要有中文出现):
在这里插入图片描述
造成错误的原因是CubeMX 对中文的支持不友好,且生成MDK 工程默认通过工程中的CMSIS 那个绿色的控件选择启动文件而不是直接添加启动文件(startup_xxx.s)到我们的工程中,而中文路径时就会找不到,有两个解决办法:
1、用CubeMX 生成的工程不要放置在包含中文路径的文件夹下;
2、添加启动文件到我们的工程中,我们新建一个application/MDK-ARM 分组,把startup_stm32f103xe.s 添加到这个分组,如图10.3.3.22 所示:
在这里插入图片描述
④关于配置的文件CubeMX 工程(.ioc 后缀)名字有中文的情况,我们建议重新新建工程或者把生成的工程文件重命名为英文。因为带中文的CubeMX 工程生成的MDK 的Output 目录有中文,MDK 也会报错,尽管可以重新设置MDK 工程的Output 目录和添加③所描述步骤的启动文件,使本次编译通过,但下次重新用CubeMX 生成工程时,仍旧需要重复修改配置。

  • 6
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行稳方能走远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值