由于Keil界面看起来年代久远,代码提示和自动格式化的功能也不是很好用,为了美化并且规范化我们的代码,这里介绍一种使用MinGW+GNU Arm Embedded Toolchain+Makefile+OpenOCD的开发方法,编辑器当然还是强大的VSCode
一、需要的工具
- 首先我们需要STM32CubeMX进行工程的创建,功能的配置以及代码生成,直接在意法半导体官网即可下载。
- 然后当然是轻便简洁的编辑器Visual Studio Code,实际上它只是一款文本编辑器而已,但丰富的插件赋予了它媲美IDE的能力,我们直接在微软找到下载就可以了。
- 然后就是MinGW,我们需要它来进行执行make编译命令,在这个链接下载 MinGW.
- 接下来是交叉编译的工具链 GNU Arm Embedded Toolchain.
- 最后是烧录工具OpenOCD,当然你手上得有ST-Link或者Jlink之类的arm仿真器才能成功下载程序 OpenOCD.
以上直接下最新板就行了,注意以上所有工具安装成功后,MinGW、GNU Arm Embedded Toolchain、OpenOCD根目录下的bin文件要加入环境变量,我们才能通过终端使用命令行来控制编译、烧录等操作。
二、配置开发环境
1.安装插件
想要使用VSCode进行代码编辑并获得代码检查、编译等功能,光有一个文本编辑器的壳子是不行的,我们需要安装一些插件来扩展它的功能,这里我直接贴上我已经安装的插件:
实际上在这里只安装一个C/C++即可,其他插件是我开发其他项目所安装的,建议可以安装一个One Dark Pro改下黑色主题,代码颜色也更好看。
如果希望你的代码更漂亮,建议安装一个Clang-Format插件,可以自己定义你的格式化风格,并且安装LLVM风格进行代码格式化,具体使用方法可以自己搜一搜,在这里我直接贴上我自己配置的,在你安装好插件,配置好Path以后,新建一个 .clang-format 文件,并在里面贴上以下内容:
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: true
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: true
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 180
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
StatementAttributeLikeMacros:
- Q_EMIT
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequires: false
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PenaltyIndentedWhitespace: 0
PointerAlignment: Right
ReflowComments: true
SortIncludes: true
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Latest
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...
切记不能有中文,将此文件放在你的工程根目录,然后打开VSCode的设置,在搜索框里搜索Editor,找到Format On Save这几个选项,按照下图配置:
这样我们按快捷键 ctrl+s 的时候就可以保存的同时并且按照我们格式化文件里配置的进行代码格式化
2.新建工程
由于按照软件包里的Template进行配置过于麻烦,所以我们直接使用意法半导体官方的图形化配置工具进行新建工程并配置功能。 记得STM32CubdeMX的安装目录不能有中文。
选择好你的目标芯片以后,进行系统和功能的配置
首先我们需要在RCC里面选择启用外部晶振,STM32是有提供内部晶振的,但是精度很低,不会有正常人会去使用STM32内部的低速晶振吧,通常我们的开发板上都搭载的8M或者25M的外部晶振,那么STM32F1系列是可以最高到72MHz的,通过内部的锁相环进行倍频使用,在这里我们要选择启用外部石英晶体。
其次我们要在SYS的Debug里面选择启用Serial Wire,否则如果你误使用了SWDIO和SWCLK这两个引脚后你的芯片将无法再通过SWD引脚烧录程序。
接下来我们去配置时钟树,选择外部高速晶振和PLL锁相环倍频进行配置,如果搞不太懂时钟树,直接设置为72MHz让它自动帮你配置也是可以的。
在这里我们就配置完了,接下来就是配置你需要使用的外设,在这里我们只做一个点灯的实验,所以我们找到GPIO外设,按照你的Led连接的引脚进行选择,如果你也是类似我这样引脚比较多的芯片,可以在下方的搜索框里进行搜索,这里我使用的是PB5和PE5两个引脚。选中它并选择该引脚的功能,在GPIO的配置里面我们可以配置成输出、输入、复用等等,以及推挽、上拉或下拉以及开漏等功能。
最后我们来到Project Manager菜单,进行工程名、工程目录、开发工具等的配置,切记目录不能有中文。在这里可以使用Keil或者STM32CubeIDE或者CLion等等,我们使用的是Mekefile,所以这里选择Makefile。然后在Code Generator进行生成代码的一些配置,在这里我选择只复制必要的库文件,并且生成单独的C文件和头文件,方便我们开发项目。
最后我们点击GENERATE CODE就可以自动生成代码了,生成完毕以后,我们找到这个文件夹,打开它,并且右键选择使用VSCode打开,你也可以先打开VSCode在里面选择打开文件夹,然后定位到该文件夹。
刚打开时你会发现一堆红线报错,这是因为C/C++插件找不到这些头文件目录,我们需要进行一些配置。
在这里我们点击 CTRL+SHIFT+P然后在搜索框搜索C/C++,选择编辑配置(json),按照生成的Makefile文件进行配置,这里我直接贴上我的配置:
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/Core/Inc",
"${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc",
"${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc/Legacy",
"${workspaceFolder}/Drivers/CMSIS/Device/ST/STM32F1xx/Include",
"${workspaceFolder}/Drivers/CMSIS/Include"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE",
"USE_HAL_DRIVER",
"STM32F103xE"
],
"windowsSdkVersion": "10.0.19041.0",
"compilerPath": "C:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2021.10/bin/arm-none-eabi-gcc.exe",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "windows-gcc-arm",
"browse": {
"path": [
"${workspaceFolder}/**",
"${workspaceFolder}/Core/Inc",
"${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc",
"${workspaceFolder}/Drivers/STM32F1xx_HAL_Driver/Inc/Legacy",
"${workspaceFolder}/Drivers/CMSIS/Device/ST/STM32F1xx/Include",
"${workspaceFolder}/Drivers/CMSIS/Include",
"${workspaceFolder}/Component/**"
]
}
}
],
"version": 4
}
具体的路径还是要根据你的安装目录和文件目录来,不可盲目复制使用。
可以看到这里有一个Component的路径,我们在工程目录里新建一个Component文件夹专门放我们自己添加的代码和功能组件,为了不破坏CubeMX为我们生成的工程目录。在下面新建一个Led文件夹,在Led文件夹内新建Led.c和Led.h文件。
我们点开main.c可以看到里面的注释非常规范,那我们为什么不直接复制过来用呢,规范我们放置头文件包含、宏定义、变量声明的位置等等。
在这里为了代码的可移植性和规范性,我使用了C++的面向对象思想,当然C语言是没有类和对象这个概念的,但是别忘了C++是由C来的,C当中我们可以使用结构体来封装我们的类,结构体里不能放函数的声明,但是我们可以声明函数指针,在C文件里给函数指针赋函数的值,这样就可以当成是把函数的原型放在类内,函数的实现放在了C文件当中。那么整个结构体内都是Public的所以不存在什么私有的概念,这也是一部分缺陷。通过将结构体变量全局化,我们就可以在其他的C文件当中使用它了。
在这里我直接贴上我的代码:
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "Led.h"
/* Private includes ----------------------------------------------------------*/
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
static void Led_SetState(LedNumber ledNum, LedStatus ledState);
/* Private user code ---------------------------------------------------------*/
Led_TypeDef Led = {
Led_SetState /* Led set status function */
};
/**
* @brief The application entry point.
* @param ledNum: ledNum_0 | ledNum_1
* @param ledState: ledOn | ledOff
* @retval int
*/
static void Led_SetState(LedNumber ledNum, LedStatus ledState) {
switch (ledState) {
case ledOn:
if (ledNum == ledNum_0)
LED0_GPIO_PORT->BSRR = (uint32_t)LED0_PIN_NUM << 16u;
else
LED1_GPIO_PORT->BSRR = (uint32_t)LED1_PIN_NUM << 16u;
break;
case ledOff:
if (ledNum == ledNum_0)
LED0_GPIO_PORT->BSRR = LED0_PIN_NUM;
else
LED1_GPIO_PORT->BSRR = LED1_PIN_NUM;
break;
}
}
/**
******************************************************************************
* @file : main.h
* @brief : Header for main.c file.
* This file contains the common defines of the application.
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __LED_H
#define __LED_H
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"
/* Private includes ----------------------------------------------------------*/
/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
#define LED0_GPIO_PORT GPIOB
#define LED0_PIN_NUM GPIO_PIN_5
#define LED1_GPIO_PORT GPIOE
#define LED1_PIN_NUM GPIO_PIN_5
/* Exported variables ---------------------------------------------------------*/
typedef enum {
ledOn = 0, /* Led turn on */
ledOff /* Led turn off */
} LedStatus;
typedef enum {
ledNum_0 = 0, /* Led 0 */
ledNum_1 /* Led 1 */
} LedNumber;
typedef struct {
void (*SetState)(LedNumber ledNum, LedStatus ledState);
} Led_TypeDef;
/* Exported functions prototypes ---------------------------------------------*/
extern Led_TypeDef Led;
/* Private defines -----------------------------------------------------------*/
#ifdef __cplusplus
}
#endif
#endif /* __LED_H */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
#include "usart.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "Led.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void) {
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
Led.SetState(ledNum_0, ledOn);
HAL_Delay(200);
Led.SetState(ledNum_1, ledOn);
HAL_Delay(200);
Led.SetState(ledNum_0, ledOff);
HAL_Delay(200);
Led.SetState(ledNum_1, ledOff);
HAL_Delay(200);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(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) {
Error_Handler();
}
/** 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;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void) {
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1) {}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line) {
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
编译烧录
最后我们要做的就是对代码进行编译和烧录了,首先找到你的MinGW安装目录,在bin文件里,复制mingw-make.exe,并粘贴一个副本出来,将它重命名为make.exe,然后在你的工程根目录里新建一个openocd.cfg文件,放入以下内容
source [find interface/stlink-v2.cfg]
source [find target/stm32f1x.cfg]
当然具体配置根据你的芯片型号和仿真器型号来选择,我们可以在openocd的根目录中的interface和target文件夹中看到可选的目标芯片和仿真器。
最后在我们需要对makefile做一些改动:
在clean up的位置做这些修改:
#######
clean:
del /q $(BUILD_DIR)
#######################################
# flash
#######################################
load: all
openocd -f ./openocd.cfg -c init -c halt -c "program $(BUILD_DIR)/$(TARGET).hex verify reset exit"
reset:
openocd -f ./openocd.cfg -c init -c halt -c reset -c shutdown
erase:
openocd -f ./openocd.cfg -c init -c halt -c "flash erase_sector 0 0 last" -c shutdown
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
@echo "build $<"
@$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
@echo "build $<"
@$(AS) -c $(CFLAGS) $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
@echo "generate elf file"
@$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(SZ) $@
然后我们点击终端,这里的终端就相当于CMD了,在里面输入make,可以看到我们的代码以及编译成功了:
我们可以在makefile里面配置代码优化等级 -Og、-Os等,具体可以自己搜搜看有什么不同,在这里为了代码更小我选择了-Os。
然后我们输入make load就可以下载到我们的板子上了
输入make clean可以清除Build
可以看到Led已经正常闪烁了