Keil MDK STM32系列(十) Ubuntu下的PlatformIO开发环境

Keil MDK STM32系列

这篇和Keil MDK没什么关系, 但是HAL库和开发方式是一样的, 也放在这个系列下吧

PlatformIO

PlatformIO是VSCode的一个扩展, 主要面向的是嵌入式的开发, 因为VSCode的跨平台属性, PlatformIO也是跨平台的. 这里主要介绍在Ubuntu20.04下的PlatformIO环境.

安装, 略

PlatformIO下的STM32烧录工具

对于STM32, PlatformIO支持的烧录工具有 blackmagic, cmsis-dap, dfu, jlink, serial, stlink, 直接选stlink就可以了.

PlatformIO下的STM32封装库

在PlatformIO下, 在Platforms中安装ST STM32, 这个Platform中包含了多个适用于STM32开发的framework

Libopencm3

没找到安装路径... 这是一套面向ARM Cortex-M架构微处理器的开源固件库, 支持STM32, Atmel SAM3x, NXP LPC, TI LM4F等系列的MCU, 对这个不熟就不介绍了

Arduino

依托于Arduino的库环境, 使用Arduino的封装接口操作STM32, 适合初学者

package的路径在 [user home]/.platformio/packages/framework-arduinoststm32 , 大小接近500MB

Cmsis

这部分类似于Keil MDK下的SPL库, 包含多个package

  • cmsis核心库的安装路径是 /home/[your user]/.platformio/packages/framework-cmsis, 大小103MB
    • 对应了STM32Cube完整库 Drivers/CMSIS/ 路径下的内容
    • 多了一个Driver目录
  • stm32f4-framework库文件安装路径是 /home/[your user]/.platformio/packages/framework-cmsis-stm32f4, 大小30MB
    • 这个库只是STM32Cube MCU Full Package 的核心定义部分, 在Github上的仓库地址: https://github.com/STMicroelectronics/cmsis_device_f4
    • 这个库有自己的版本号v2.6.x, 容易与完整库的版本号混淆, 其与CMSIS CoreFull MCU package的对应关系在README.md的表格中, 现在最新的是v2.6.7, 对应的完整库版本为v1.26.2
    • 这个库与Windows Keil5 MDK下使用的标准外设库Standard peripherals library不兼容, 后者已经不再更新, 最高版本到1.8.0
  • STM32Cube完整库的仓库

综上, STM32F4的CMSIS环境下是没有可用的标准外设库(Standard peripherals library)的, 尝试过用1.8.0的标准外设库, 编译有不少错误, 所以用这个开发的话

  1. 要么自己修订标准外设库, 使其兼容v2.6.x的CMSIS核心库, 这个需要对各外设的变量和结构体很熟, 我这样刚入门的估计是搞不定
  2. 要么不用标准外设库, 纯使用核心库变量开发. 这个难度也不小, 类似于回到8051开发的模式了, 每写一步都要查寄存器手册.
  3. 要么直接用STM32Cube的完整外设库. 不过既然都用了STM32Cube的库了, 为什么不直接选framework时就选STM32Cube呢?

Stm32cube

文档: https://docs.platformio.org/en/latest/frameworks/stm32cube.html

和Windows下的STM32CubeMX使用了相同的HAL库, package路径 [user home]/.platformio/packages/framework-stm32cubef4 , 大小 78.3MB

这个package下的内容, 就是STM32CubeF4仓库的内容, 当前版本是1.26.2.

使用STM32CubeF4开发的项目, 用这个Framework是可以直接编译的.

STM32CubeF4 的文件结构

这个库有三个目录

STM32CubeF4/Drivers

这里包含了核心库 CMSIS, HAL库 STM32F4xx_HAL_Driver, 还有众多的外设支持, 简直是个宝库

├── Drivers
│   ├── BSP
│   │   ├── Adafruit_Shield
│   │   ├── Components
│   │   │   ├── ampire480272
│   │   │   ├── ampire640480
│   │   │   ├── Common
│   │   │   ├── cs43l22
│   │   │   ├── exc7200
│   │   │   ├── ft6x06
│   │   │   ├── i3g4250d
│   │   │   ├── ili9325
│   │   │   ├── ili9341
│   │   │   ├── l3gd20
│   │   │   ├── lis302dl
│   │   │   ├── lis3dsh
│   │   │   ├── ls016b8uy
│   │   │   ├── lsm303agr
│   │   │   ├── lsm303dlhc
│   │   │   ├── mfxstm32l152
│   │   │   ├── n25q128a
│   │   │   ├── n25q256a
│   │   │   ├── n25q512a
│   │   │   ├── nt35510
│   │   │   ├── otm8009a
│   │   │   ├── ov2640
│   │   │   ├── ov5640
│   │   │   ├── s25fl512s
│   │   │   ├── s5k5cag
│   │   │   ├── st7735
│   │   │   ├── st7789h2
│   │   │   ├── stmpe1600
│   │   │   ├── stmpe811
│   │   │   ├── ts3510
│   │   │   └── wm8994
│   │   ├── STM32412G-Discovery
│   │   ├── STM32446E_EVAL
│   │   ├── STM32469I-Discovery
│   │   ├── STM32469I_EVAL
│   │   ├── STM324x9I_EVAL
│   │   ├── STM324xG_EVAL
│   │   ├── STM32F401-Discovery
│   │   ├── STM32F411E-Discovery
│   │   ├── STM32F413H-Discovery
│   │   ├── STM32F429I-Discovery
│   │   ├── STM32F4-Discovery
│   │   ├── STM32F4xx-Nucleo
│   │   └── STM32F4xx_Nucleo_144
│   ├── CMSIS
│   │   ├── Core
│   │   ├── Core_A
│   │   ├── Device
│   │   ├── DSP
│   │   ├── Include
│   │   ├── Lib
│   │   │   └── GCC
│   │   ├── NN
│   │   ├── RTOS
│   │   └── RTOS2
│   └── STM32F4xx_HAL_Driver
│       ├── Inc
│       └── Src

STM32CubeF4/Middlewares

这个目录下是USB功能封装和一些常用的第三方库, 例如FatFS, LwIP等

├── Middlewares
│   ├── ST
│   │   ├── STemWin
│   │   ├── STM32_Audio
│   │   │   └── Addons
│   │   │       └── PDM
│   │   ├── STM32_USB_Device_Library
│   │   ├── STM32_USB_Host_Library
│   │   └── TouchGFX
│   └── Third_Party
│       ├── FatFS
│       ├── FreeRTOS
│       ├── LibJPEG
│       ├── LwIP
│       └── mbedTLS

STM32CubeF4/Utilities

这里是一些工具和字体

└── Utilities
    ├── CPU
    ├── Fonts
    ├── Log
    └── Media
        ├── Audio
        ├── Pictures
        └── Video

使用Stm32cube创建项目

打开PlatformIO:Home, 如果没装 Platform:ST STM32 的, 先在 Platform 里装一下

  1. 点击 New Project,
  2. 填入项目名称,
  3. 在 Board 中输入 stm32f401 在过滤结果的列表中, 选择 stm32f401cc, 如果是常见的 WeAct Studio BlackPill, 可以直接在底下找到对应的专门的board
  4. 选择 Framework 为 STM32Cube
  5. 如果不希望建在默认目录的话, 取消 Use Default Location 勾选, 指定位置, PlatformIO 会创建项目目录

platformio.ini

这个文件的内容如下, 一个项目里可以有多个env, 可以指定一个默认的, 在使用快捷键时, 会执行默认env的编译和写入.

[env:blackpill_f401cc]
platform = ststm32
board = blackpill_f401cc
framework = stm32cube

;通用的 stm32f401cc 配置
[env:stm32f401cc]
platform = ststm32
board = genericSTM32F401CC
framework = stm32cube
upload_protocol = stlink
board_build.stm32cube.custom_config_header = yes 

参数说明:

  • upload_protocol 用于指定不同的写入方式
  • board_build.stm32cube.custom_config_header = yes 禁止platformio自动生成stm32f4xx_hal_conf.h. 默认情况下, 在项目启动或编译时, platformio会自动在库文件目录, 基于stm32f4xx_hal_conf_template.h复制创建stm32f4xx_hal_conf.h, 而HAL项目中这个配置文件一般是和main.c一起放在用户代码目录下的, 如果按默认的方式, 编译时会优先使用库目录下的文件, 用户自己配置的就失效了. 加上这个选项可以禁止platformio自己生成这个文件.

.vscode/c_cpp_properties.json

这个文件很重要, 因为任何 include 的错误, 都可以在这里检查. 这个文件是 PlatformIO 自动生成的, 所以不需要去改它, 如果发现你修改了platformio.ini 后它没有更新, 重新打开 VS Code 就可以了.

stm32f4xx_hal_conf.h

从 [user home]/.platformio/packages/framework-stm32cubef4/Drivers/STM32F4xx_HAL_Driver/Inc 中复制 stm32f4xx_hal_conf_template.h 到 src 目录, 并重命名为 stm32f4xx_hal_conf.h

需要修改的两处

  1. 模块启用部分. define xxxx ENABLED 这部分只需要保留下面的这些, 其它全部注释掉, 只有用到的才需要取消注释
#define HAL_MODULE_ENABLED

#define HAL_GPIO_MODULE_ENABLED
#define HAL_EXTI_MODULE_ENABLED
#define HAL_DMA_MODULE_ENABLED
#define HAL_RCC_MODULE_ENABLED
#define HAL_FLASH_MODULE_ENABLED
#define HAL_PWR_MODULE_ENABLED
#define HAL_CORTEX_MODULE_ENABLED
  1. 设置外部晶振频率, 设置成板载晶振的频率
#define HSE_VALUE    8000000U /*!< Value of the External oscillator in Hz */

项目文件

以下文件都直接在 src 目录下创建, 演示A6, A7两个PIN连接的LED间隔2秒闪灯

main.c


#include "main.h"

void SystemClock_Config(void);
void SystemClock_Config_HSE_84MHz(void);
static void MX_GPIO_Init(void);

int main(void)
{
  HAL_Init();
  SystemClock_Config_HSE_84MHz();
  MX_GPIO_Init();
  while (1)
  {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6 | GPIO_PIN_7, GPIO_PIN_SET);
    HAL_Delay(2000);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6 | GPIO_PIN_7, GPIO_PIN_RESET);
    HAL_Delay(2000);
  }
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** 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.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 84;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  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();
  }
}

void SystemClock_Config_HSE_84MHz(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  
  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();
  }
}

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);

  /*Configure GPIO pins : PA6 PA7 */
  GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

}

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

main.h

#ifndef __MAIN_H
#define __MAIN_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

void Error_Handler(void);

#ifdef __cplusplus
}
#endif

#endif /* __MAIN_H */

stm32f4xx_hal_msp.c

#include "stm32f4xx_hal.h"

void HAL_MspInit(void)
{
  __HAL_RCC_SYSCFG_CLK_ENABLE();
  __HAL_RCC_PWR_CLK_ENABLE();
}

stm32f4xx_it.c

#include "stm32f4xx_hal.h"
#include "stm32f4xx_it.h"

/******************************************************************************/
/*           Cortex-M4 Processor Interruption and Exception Handlers          */
/******************************************************************************/

void NMI_Handler(void)
{
  while (1)
  {
  }
}

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)
{
}

void SysTick_Handler(void)
{
  HAL_IncTick();
}

stm32f4xx_it.h

#ifndef __STM32F4xx_IT_H
#define __STM32F4xx_IT_H

#ifdef __cplusplus
 extern "C" {
#endif

/* Exported functions prototypes ---------------------------------------------*/
void NMI_Handler(void);
void HardFault_Handler(void);
void MemManage_Handler(void);
void BusFault_Handler(void);
void UsageFault_Handler(void);
void SVC_Handler(void);
void DebugMon_Handler(void);
void PendSV_Handler(void);
void SysTick_Handler(void);

#ifdef __cplusplus
}
#endif

#endif /* __STM32F4xx_IT_H */

STM32F401CCxx 可用的 SystemClock_Config()

配置不正确的系统时钟会导致系统卡死, 下面两个方法是测试可用的

/**
* Generated by STM32Cube 1.26.x
* *) Enable HSE(25MHz), No LSE(32.768KHz)
* *) No PLL, No prescaler, HSE->SYSCLK->PHBPrescaler=1->AHB,APB1,APB2...
*/
void SystemClock_Config_HSE_25MHz(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  //RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // Make APB2 12.5MHz

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
* Generated by STM32Cube 1.26.x
* *) Enable HSE(25MHz), No LSE(32.768KHz)
* *) Enable PLL
* *) HSE->PLL->/25->*168->/2->PPLCLK->SYSCLK(84MHz)->APB1 /2-> APB1
*/
void SystemClock_Config_HSE_84MHz(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 25;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  
  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();
  }
}

用UART输出printf的宏定义

因为sdcc和keil5 mdk下的编译工具不一样, 所以这部分的定义也不一样, 可以用下面的宏语句统一处理

#if defined(__GNUC__)
int _write(int fd, char *ptr, int len)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, 0xFFFF);
    return len;
}
#elif defined (__ICCARM__)
size_t __write(int handle, const unsigned char * buffer, size_t size)
{
    HAL_UART_Transmit(&huart1, (uint8_t *) buffer, size, HAL_MAX_DELAY);
    return size;
}
#elif defined (__CC_ARM)
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}
#endif

或者

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */

/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

参考

posted on 2021-09-13 01:46  Milton  阅读(1852)  评论(0编辑  收藏  举报

导航