本次例程会建立一个STM32F1的HAL(Hardware Abstract Layer)库的Keil工程模板,然后点亮一个LED,以后的开发可以基于该模板进行。有的童鞋可能会觉得直接从网上下一个模板就行了,没必要自己搭建;但事实上是我们自己搭建模板并不仅仅是为了之后用起来方便,更重要的是了解STM32工程的组成,启动顺序等知识。其实很多初做嵌入式开发的人只是面向百度开发,没有建立起自己知识框架的意识;我曾经问过身边用过STM做开发的一些朋友,向上都用过各种外设但自己搭建个工程却困难重重。如果想要把STM32学透,还是应该多尝试使用官方提供的芯片数据手册等解决问题而不是查百度。
好的我们言归正传,本系列使用的核心板主控为STM32F103RCT6,芯片内建256KB FLASH、48KB SRAM,板载了SPI FLASH、USB转TTL、一个LED与一个按键,其余引脚均用排针引出,童鞋们也可以使用其他开发板,开发过程大同小异。
一、准备
本教程所需的内容如下:
- STM32F1核心板
- JLINK或者其他调试器(已装好驱动)
- Keil开发环境
- STM32F1的HAL库
所需的前两项都可用淘宝解决,也可以自己制作,百度一个驱动装好。其次我们需要安装Keil5环境,这个可以直接从KEIL的官网上下到,注意要下三个东西:keil5本体、CMSIS包、STM32F1器件包,下完之后一路安装即可。再次我们需要去ST官网上下载STM32F1的HAL库,我们需要先创建一个ST帐号,然后在搜索框中搜索HAL,选择工具与软件,下图中就是:
下载到本地后解压缩,然后将文件夹下的Drivers->CMSIS与Drivers->STM32F1xx_HAL_Driver放到一个你觉得可以存放库文件的位置,例如:D:\STM32\lib\f1,之后就可以开始下一步了。
二、搭建
在打开KEIL之前,我们先创建一个文件夹用来存放模板,这里以为D:\STM32\Template\为例,再在该文件夹下按照如图所示的文件结构创建文件夹(也可按自己想法组织文件):
然后我们需要准备几个工程所需的文件,需要注意有些从库中拷出的文件是只读的,记得右键属性取消只读。
- stm32f1xx_hal_msp.c
该文件是HAl库提供的存放芯片支持包(MCU Support Package)函数的位置,该文件在库文件的Drivers->STM32F1xx_HAL_Driver->Src中有模板,名为stm32f1xx_hal_msp_template.c,我们可以将其拷到Template\Core中改名为stm32f1xx_hal_msp.c。该文件中的HAL_MspInit函数是在HAL库初始化完成后被调用,我们计划在这里使能外部晶振,配置芯片时钟,让其运行在72MHz。
这里给出适合于STM32F103RCT6的配置部分,F1系列应该都可用。
void SystemClockConfig(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
/* 使能外部时钟 */
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
while(1);
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
/* 配置RCC */
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
while(1);
}
}
/**
* @brief Initializes the Global MSP.
* @retval None
*/
void HAL_MspInit(void)
{
SystemClockConfig();
}
- stm32f1xx_it.c与stm32f1xx_it.h
该文件存放的是中断函数,本次我们只需要其中的滴答定时器中断函数SysTick_Handler来给HAL库提供时间基准,其他函数可以暂不添加到文件。这个文件可以从下载的库文件中找个例程的替代,例如这次使用STM32Cube_FW_F1_V1.8.0\Projects\STM32F103RB-Nucleo\Templates\Src下的stm32f1xx_it.c与STM32Cube_FW_F1_V1.8.0\Projects\STM32F103RB-Nucleo\Templates\Inc下的stm32f1xx_it.h,将两个文件拷到Template\Core和Template\Core\include下。
- stm32f1xx_hal_conf.h
该文件是HAL库的配置文件,可以实现对HAL库的裁剪。从库文件的Drivers->STM32F1xx_HAL_Driver->Inc中把stm32f1xx_hal_conf_template.h删掉后面的template后放到Template\Core\include下。这是我裁剪的部分:
#define HAL_MODULE_ENABLED
#define HAL_ADC_MODULE_ENABLED
#define HAL_CAN_MODULE_ENABLED
/* #define HAL_CAN_LEGACY_MODULE_ENABLED */
#define HAL_CEC_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED
#define HAL_CRC_MODULE_ENABLED
#define HAL_DAC_MODULE_ENABLED
#define HAL_DMA_MODULE_ENABLED
//#define HAL_ETH_MODULE_ENABLED
#define HAL_EXTI_MODULE_ENABLED
#define HAL_FLASH_MODULE_ENABLED
#define HAL_GPIO_MODULE_ENABLED
// #define HAL_HCD_MODULE_ENABLED
#define HAL_I2C_MODULE_ENABLED
#define HAL_I2S_MODULE_ENABLED
#define HAL_IRDA_MODULE_ENABLED
#define HAL_IWDG_MODULE_ENABLED
// #define HAL_NAND_MODULE_ENABLED
// #define HAL_NOR_MODULE_ENABLED
// #define HAL_PCCARD_MODULE_ENABLED
// #define HAL_PCD_MODULE_ENABLED
#define HAL_PWR_MODULE_ENABLED
#define HAL_RCC_MODULE_ENABLED
#define HAL_RTC_MODULE_ENABLED
// #define HAL_SD_MODULE_ENABLED
// #define HAL_SMARTCARD_MODULE_ENABLED
#define HAL_SPI_MODULE_ENABLED
// #define HAL_SRAM_MODULE_ENABLED
#define HAL_TIM_MODULE_ENABLED
#define HAL_UART_MODULE_ENABLED
#define HAL_USART_MODULE_ENABLED
#define HAL_WWDG_MODULE_ENABLED
// #define HAL_MMC_MODULE_ENABLED
- main.c与main.h
这两个文件大家肯定是很熟悉的,其中main.h不是必须的,不过我还是习惯加上了,这两个文件我们把他安排在Template\User和Template\User\include中。下面给出一个点灯的例子。
// main.c
#include "main.h"
int main()
{
HAL_Init();
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能PA时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 使能PA8 */
GPIO_InitStruct.Pin=GPIO_PIN_8;
GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull=GPIO_PULLUP;
GPIO_InitStruct.Speed=GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
while(1)
{
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
}
}
// main.h
#ifndef __MAIN__H__
#define __MAIN__H__
#include "stm32f1xx_hal.h"
#endif
经过一顿操作后的文件结构如下图:
然后就可以打开KEIL5了,选择Project->New uVision Project在弹出的界面中找到Template\MDK-ARM文件夹,工程名为Template;然后选择在器件库里选择STM32F103RC 然后OK。
在弹出的组件中选择CMSIS->CORE 点OK。
点击Project->Option for Target… 选项卡选择Target,编译器我们选择V6;
选项卡选择C/C++,添加宏USE_HAL_DRIVER,STM32F103xE;Warnings选择AC-5 like Warnings;
添加下列include path:
- ..\Core\include
- ..\Drivers\include
- ..\..\lib\f1\STM32F1xx_HAL_Driver\Inc
- ..\..\lib\f1\CMSIS\Device\ST\STM32F1xx\Include
- ..\User\include
按如下结构安排文件:
其中CORE文件夹中除了我们之前准备好的stm32f1xx_hal_msp.c和stm32f1xx_it.c之外还需一个Cortex-M3的接口文件system_stm32f1xx.c和汇编写的启动文件startup_stm32f103xe.s,这两个文件都可用在库文件的CMSIS\Device\ST\STM32F1xx\Source\Templates和下面的arm路径找到,启动文件具体选择哪个可以参考stm32f1xx.h中的注释说明
因为我们是F103RCT6所以选择startup_stm32f103xe.s选择汇编文件时需要将文件类型选为All files
HAL文件夹下我们将lib\f1\STM32F1xx_HAL_Driver\Src中带HAL但不带template的源文件全部加到工程里。
USER中把我们的Template\User\main.c加进入就行。
接下来就到了激动人心的时刻了,我们点下Rebuild按键
如果结果和下图一样,那我们的工程就算搭建完成了。
三、烧写
使用你的Jlink(或者你的仿真器)连接好你的板子,我们再次点击Option for Target…到Debug页面,选择好仿真器型号,然后点击Settings
根据你的连接方式选择是JTAG还是SW
如果你的连接正确,现在就可以在右侧看到你的设备了,这时我们再选择Flash Download选项卡,勾选上Reset and Run,这个选项可以让你每次下完程序后不用手动点一下复位。
然后我们一路确定,点击下载按键。
出现下面的提示就证明你的下载已经成功了
不出意外,你的LED现在应该已经开始闪烁了起来,我们的任务也就大功告成了。
四、总结
我们刚刚用一大堆的文字代码图片终于把最简单的搭建工程给做完了,在讲述基本的使用方法的同时还顺便点了个灯,希望大家能够从中有所收获。使用过的工程文件可以在下面链接中获取,我们下期讲讲如何让只会亮灭的灯变成更人性化的呼吸灯,扫一扫下方二维码关注我,我们下期再见。