PlatformIO IDE(VSCode) - stm32cube 框架的工程

前些天知乎网友有问到 PIO 中 stm32 的 cubemx 框架工程中闪灯程序不能正常工作的问题,猜测可能是没有进行系统时钟配置导致的。本文就以 LED 闪烁为例讲一下如何新建一个 stm32cube 框架的工程,并且将 LED 点亮。

测试平台

  • PlatformIO IDE (VSCode)
  • stm32f103c8t6 最小系统板,板载 LED 连接在 PC13 管脚,低电平点亮
  • stlink v2 仿真调试器

测试目标:创建 stm32cube 框架的工程项目,实现 LED 周期闪烁的代码

创建工程项目

这一步很简单,注意选择框架 stm32cube^[stm32cube 对应的就是 cubemx 库,详见STM32Cube],开发板选择 STM32F103C8(20k RAM. 64k Flash)(Generic),名称随便,最后点击创建即可:

添加例程代码

按照 STM32Cube HAL and Nucleo-F401RE: debugging and unit testing - Adding Code to the Generated Project ^[STM32Cube HAL and Nucleo-F401RE: debugging and unit testing] 描述的过程添加适合自己开发板的代码(修改对应的 LED 管脚即可)。

右键 src 文件夹,新建两个文件,一个是 main.h 另一个是 main.c

mian.h 中添加以下代码,如果您的板载 LED 连接到其它管脚,请相应修改关于 LED 的三个宏定义:

#ifndef MAIN_H
#define MAIN_H

#include "stm32f1xx_hal.h"

#define LED_PIN GPIO_PIN_13
#define LED_GPIO_PORT GPIOC
#define LED_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()

#endif  // MAIN_H

mian.c 中添加以下代码:

#include "main.h"

void LED_Init(void);

int main(void) {
  HAL_Init();
  LED_Init();

  while (1) {
    // LED 翻转
    HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN);
    HAL_Delay(1000);
  }
}

void LED_Init(void) {
  // LED GPIO 初始化
  LED_GPIO_CLK_ENABLE();
  GPIO_InitTypeDef GPIO_InitStruct;
  GPIO_InitStruct.Pin = LED_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct);
}

void SysTick_Handler(void) { HAL_IncTick(); }

确保把 stm32f103c8t6 的最小系统板通过 stlink 连接到电脑 usb,编译并上传程序:

what? LED 竟然没有如期的闪动起来!

解决问题

代码都正确呀,为什么程序没有正常工作呢?首先怀疑对象是系统时钟,用示波器测试了一下芯片的高速晶振管脚,竟然没有起振,哎呀,我们确实没有添加系统时钟配置的代码,再次对照一下用 STMCubeMX^[STM32CubeMX is a graphical tool that allows a very easy configuration of STM32 microcontrollers and microprocessors, as well as the generation of the corresponding initialization C code for the Arm® Cortex®-M core or a partial Linux® Device Tree for Arm® Cortex®-A core), through a step-by-step process.——STM32CubeMX] 生成的普通项目的源码,确实我们的工程中缺少了对于时钟的配置。

为了解决这个问题,参考了 STMCubeMX 项目,笔者建议除了添加时钟的配置之外,还要加一些可能以后会用到的处理函数。这里我们添加两个新的文件 sys_basic.hsys_basic.c

文件 sys_basic.h 内容如下:

#ifndef SYS_BASIC_H
#define SYS_BASIC_H

void _Error_Handler(char*, int);
#define Error_Handler() _Error_Handler(__FILE__, __LINE__)

void SystemClock_Config(void);

#endif

文件 sys_basic.c 内容如下:

#include "sys_basic.h"
#include "stm32f1xx_hal.h"

/** System Clock Configuration
 */
void SystemClock_Config(void) {
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

  /**Initializes the CPU, AHB and APB busses clocks
   */
  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(__FILE__, __LINE__);
  }

  /**Initializes the CPU, AHB and APB busses 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(__FILE__, __LINE__);
  }

  /**Configure the Systick interrupt time
   */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000);

  /**Configure the Systick
   */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/**
 * @brief  This function is executed in case of error occurrence.
 * @param  None
 * @retval None
 */
void _Error_Handler(char* file, int line) {
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  while (1) {
  }
  /* USER CODE END Error_Handler_Debug */
}

void NMI_Handler(void) {}

void HardFault_Handler(void) {
  while (1) {
  }
}

void MemManage_Handler(void) {
  while (1) {
  }
}

void BusFault_Handler(void) {
  while (1) {
  }
}

void UsageFault_Handler(void) {
  while (1) {
  }
}

void SVC_Handler(void) {}

void DebugMon_Handler(void) {}

void PendSV_Handler(void) {}

时钟初始化在函数 SystemClock_Config 中实现,这里时钟源选择使用 8M 高速晶振、9倍频等等常用的时钟配置,在 main.c 中包含头文件 sys_basic.h 并在主函数 HAL_Init(); 后加入时钟初始化函数调用 SystemClock_Config();

此时编译上传程序,可以看到 LED 如期灵动了起来!

调试程序

调试程序与使用 Arduino 框架时一致,需要在配置文件中指定调试工具,这里使用的是 stlink ,所以 paltformio.ini 的内容如下:

[env:genericSTM32F103C8]
platform = ststm32
board = genericSTM32F103C8
framework = stm32cube
debug_tool = stlink

点击 F5 按键便可以进行程序调试了!

总结

本文简单讲解了创建 stm32cube 框架的 PIO 工程的过程、遇到的问题及解决方法,希望对初次使用 PIO 开发 stm32cube 项目的同学有所帮助!