1 前言
接触stm32的时间也比较长,但是长期依赖于cubemx,总感觉缺少了什么。于是,我学习了如何一步一步自己创建一个工程,基于HAL库的版本。
我使用的是野火指南者,STM32F103VET6
2 准备部分
2.1 关于STM32F103VET6
STM32F103VET6具有100引脚 ,512k的Flash和64k的SRAM。官方介绍如下:
2.2 HAL固件库下载
STM32的官方网址:https://www.st.com.在ST官网搜索STM32F1 HAL
2.3 安装STM32F1的Pack
官网:http://www.keil.com/dd2/pack/找到对应要下载的包。下载后,双击即可自动安装
2.4 STM32CubeF1固件包说明
2.4.1 HAL固件库
打开HAL固件库压缩包会有以下文件:
2.4.2 部分少用的文件说明
文件夹 | 作用 |
---|---|
_htmresc | 这个文件夹放的是一些图片和网站的资料,感觉没什么用。 |
Documentation | 里面是一个STM32CubeF1的说明文档,而且是英文的 |
Utilities | 该文件夹下面是一些其他组件,基本没怎么使用 |
Middlewares | 如果做基本的开发,这个文件夹也不怎么使用。 |
2.4.3 Middlewares文件夹的子文件夹说明
Middlewares文件夹中包含了ST和Third_Party两个子文件夹,它们的内容如下:
文件夹 | 作用 |
---|---|
ST\STemWim | 存放的是STemWin工具包。 |
ST\STM32_TouchSensing_Library | 存放的是STM32电容触摸支持包 |
ST\STM32_USB_Device_Library | USB从机设备支持包。 |
ST\STM32_USB_Host_Library | USB主机设备支持包。 |
Third_Party\FatFs | FAT文件系统支持包。 |
Third_Party\FreeRTOS | FreeRTOS实时系统支持包。 |
2.4.4 Drivers文件夹的子文件夹说明
文件夹 | 作用 |
---|---|
BSP文件夹 | 也叫板级支持包,该包提供的是直接与硬件打交道的API,如触摸屏,LCD,SRAM以及EEPROM等板载硬件资源驱动。该文件夹中还有多种ST官方Discovery开发板,Nucleo开发板以及EVAL板的硬件驱动API文件,如果要编写相关的硬件驱动,可以参考这些文件夹中的程序。 |
CMSIS文件夹 | 该文件夹内包含的是符号CMSIS标志的软件抽象层组件的相关文件。主要包括DSP库(DSP_LIB文件夹),Cortex-M内核及其设备文件(Include文件夹),微控制器专用头文件/启动代码/专用系统文件等(Device文件夹)。 |
STM32F1xx_HAL_Driver文件夹 | 该文件夹包含了所有的STM32F1xx系列HAL库头文件和源文件,也就是所有底层硬件抽象层API声明和定义。它的作用是屏蔽了复杂的硬件寄存器操作,统一了外设的接口函数。该文件夹包含Src和Inc两个子文件夹,其中Src子文件夹存放的是.c源文件,Inc子文件夹存放的是与之对应的.h头文件。每个.c源文件对应一个.h头文件。源文件名称基本遵循stm32f1xx_hal_ppp.c定义格式,头文件名称基本遵循stm32f1xx_hal_ppp.h定义格式。 |
2.4.5 Projects文件夹
文件夹 | 作用 |
---|---|
Projects文件夹 | 该文件夹存放的是一些可以直接编译的实例工程。每一个文件夹对应一个ST官方的Demo板。 |
通过上面的介绍可以看出在STM32CubeF1固件包中用到的就**Drivers、Projects**文件夹中的文件。
3 库函数版工程模板的创建——HAL库版本
3.1 工程文件夹
首先要创建一个用来存放keil工程文件的文件夹,包括以下几个
文件夹 | 作用 | 存放的文件 |
---|---|---|
Doc | 用来存放程序说明的文件,由写程序的人添加 | 工程说明:.txt |
Libraries | 存放的是HAL库文件 | ①CMSIS:存放和CM3内核有关的库文件②STM32F1xx_HAL_Driver:STM32外设库文件 |
Listing | 存放编译器编译时产生的C/汇编/连接的列表清单 | 暂时为空 |
Output | 存放编译产生的调试信息、hex文件、预览消息、封装库等 | 暂时为空 |
Project | 用来存放工程 | 暂时为空 |
User | 用户编写的驱动文件 | ①stm32f1xx_hal_conf.h:用来配置库的头文件②stm32f1xx_it.h stm32f1xx_it.c:中断相关的函数都在这个文件编写,暂时为空③mian.c:main函数文件 |
3.2 新建工程文件
3.2.1 选择CPU
3.2.2 在线添加库文件(非常慢、改手动添加)
3.2.3 添加组文件夹
最终成效如下:
在新建的工程中添加 5 个组文件夹,用来存放各种不同的文件,文件从本地建好的工程文件夹下获取(建立文件夹后要把文件手动放进去),双击组文件夹就会出现添加文件的路径,然后选择文件即可.
3.2.4 添加文件
模板文件从HAL固件库的压缩包中的Drivers、Projects文件夹中寻找
解压以下两个文件夹
将Drivers文件夹中的子文件CMSIS文件夹中的include、Device文件夹复制添加到工程模板template中的CMSIS文件夹中
需要添加的文件的位置
组文件名 | 要添加的文件 | 位置 |
---|---|---|
STARTUP | startup_stm32f101xe.s | template\Libraries\CMSIS\Device\ST\STM32F1xx\Source\Templates\arm |
STM32F1xx_HAL_Driver | stm32f1xx_hal.c | template\Libraries\STM32F1xx_HAL_Driver\Src |
STM32F1xx_HAL_Driver | stm32f1xx_hal_ppp.c(ppp代表外设名称) | template\Libraries\STM32F1xx_HAL_Driver\Src |
USER | main.h stm32f1xx_hal_conf.h stm32f1xx_it.h | stm32cube_fw_f1_v160\Projects\STM32F103RB-Nucleo\Templates\Inc |
USER | main.c system_stm32f1xx.c stm32f1xx_it.c | stm32cube_fw_f1_v160\Projects\STM32F103RB-Nucleo\Templates\Src |
3.2.5 魔术棒设置
1、target
2、Output
将输出文件夹定位在工程目录下的Output文件夹中。如果想在编译过程中产生hex文件,则按一下勾选。
3、Listing
将输出文件定位在工程目录中的Listing文件夹中
4、C/C++
在该选项卡中添加处理宏和编译器编译的时候查找的头文件路径。如果头文件添加有误,编译的时候会报错找不到头文件。
3.2.6 仿真器配置
3.2.7 配置CPU
这一步的配置也不是配置一次之后完事,常常会因为各种原因需要重新选择,当你下载的时候,提示说找不到 Device 的时候,请确保该配置是否正确。有时候下载程序之后,不会自动运行 ,手动复位的时候,也回来看看这里的“Reset and Run”配置是否失效 。指南者/霸道/拂晓开发板上的 STM32 芯片的 FLASH 大小是 512kByte ,所以这里选择 512k 的容量,F103-MINI 开发板的要选 256K,如果使用的是其他型号的,要根据实际情况选择
3.3 细节调整
3.3.1 首次编译
将44行的#include "stm32f1xx_nucleo.h"删除,再编译就没发现错误了。
再全部编译,若有错误查看前面的步骤,是否多添加了文件或者少添加了文件,或者遗漏了步骤。
3.3.2 系统初始化之后的中断优先级分组号
默认情况下调用HAL初始化函数HAL_Init之后,会将中断优先级分组设置为4,可以通过修改HAL_Init函数的内容来更改分组。
打开stm32f1xx_hal.c文件,找到HAL_Init函数,如下图:
修改173行的HAL_NVIC_SetPPriorityGrouping函数中的参数即可。
3.3.3 系统初始化之后的时钟设置
关于时钟的设置,需要注意stm32f1xx_hal_conf.h文件里面HSE_VALUE宏定义的值,这个值是外部晶振大小的值,如果跟实际值对不上,那么跑的时钟可能会跟预计的不同,需要注意。比如外部晶振用的是8M,那么HSE_VALUE宏的值应该设为8000000U,如下图:
main.c模板中的SystemClock_Config(void)函数是初始化内部晶振。内部晶振最好频率为64M。64M的频率足够一般的外设使用,但是由于原来的SYSCLK=72M,会导致那些外设的时钟改变,打乱外设的工作。HSI是作为HSE故障时的替代,所以HSI一般是不使用的。当HSE故障时,还是需要采取报警措施。
因此,需要将模板中的初始化改外外部晶振的。**72M频率**代码1如下:
/**
* @brief System Clock Configuration
* The system Clock is configured as follow :
* System Clock source = PLL (HSE)
* SYSCLK(Hz) = 72000000
* HCLK(Hz) = 72000000
* AHB Prescaler = 1
* APB1 Prescaler = 2
* APB2 Prescaler = 1
* HSE Frequency(Hz) = 8000000
* HSE PREDIV1 = 1
* PLLMUL = 9
* Flash Latency(WS) = 2
* @param None
* @retval None
*/
void SystemClock_Config(void)
{
RCC_ClkInitTypeDef clkinitstruct = {0};
RCC_OscInitTypeDef oscinitstruct = {0};
/* Enable HSE Oscillator and activate PLL with HSE as source */
oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
oscinitstruct.HSEState = RCC_HSE_ON;
oscinitstruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
oscinitstruct.PLL.PLLState = RCC_PLL_ON;
oscinitstruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
oscinitstruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
{
/* Initialization Error */
while(1);
}
/* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2
clocks dividers */
clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
{
/* Initialization Error */
while(1);
}
}
**8M频率**代码2如下:
/**
* @brief System Clock Configuration
* The system Clock is configured as follow :
* System Clock source = PLL (HSE)
* SYSCLK(Hz) = 16000000
* HCLK(Hz) = 8000000
* AHB Prescaler = 2
* APB1 Prescaler = 2
* APB2 Prescaler = 1
* HSE Frequency(Hz) = 8000000
* HSE PREDIV1 = 1
* PLLMUL = 2
* Flash Latency(WS) = 2
* @param None
* @retval None
*/
void SystemClock_Config(void)
{
RCC_ClkInitTypeDef clkinitstruct = {0};
RCC_OscInitTypeDef oscinitstruct = {0};
/* Enable HSE Oscillator and activate PLL with HSE as source */
oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
oscinitstruct.HSEState = RCC_HSE_ON;
oscinitstruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
oscinitstruct.PLL.PLLState = RCC_PLL_ON;
oscinitstruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
oscinitstruct.PLL.PLLMUL = RCC_PLL_MUL2;
if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)
{
/* Initialization Error */
while(1);
}
/* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2
clocks dividers */
clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV2;
clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;
clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;
if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)
{
/* Initialization Error */
while(1);
}
}
用上述代码替代原有的SystemClock_Config(void)程序即可
再完全编译即可。
4 编者说明
本文参考了野火出版的《STM32HAL库开发实战指南—基于F103指南者》的第11章内容和网友新建基于STM32F103ZET6的工程-HAL库版本的主要内容,并进行改编,使之适用于指南者STM32F103VET6的版本。并且特别感谢一个学长在我首次搭建该工程是遇到了一个问题给了我很大的帮助。这个问题的解决方案我早写出了对应的博客,网址如下:[STM32问题解决(1)]类型问题解决编译错误:XXX.axf: Error: L6218E: Undefined symbol xxx (referred from xxxx.o).
本来在3月中旬的时候就已经完成了工程的搭建,但是由于种种原因一直拖更到了现在。
非常感谢网上的开源和教程,使我逐渐的学习了一些知识。对各个教程的不足之处进行的增补,使得新手更加容易获得更加详尽的教程。
注:在我参考的教程新建基于STM32F103ZET6的工程-HAL库版本中,编译结束后,作者还进行了其他知识的拓展,我没有把他们一一列出来,感兴趣的朋友可以进入他的博客中查看详情。