STM32CubeMXで生成したプロジェクトでC++を使う方法

# 概要
この記事ではSTM32CubeMXを用いて生成したプロジェクトで、C++を使用する方法を紹介します。
CubeMXで生成したプロジェクトは全てC言語で書かれていますが、少し編集すればC++も使えるようになります。

# 準備
[この記事](https://qiita.com/harashi10969/items/996c183a9993dd1a44fe)のようにCubeMXを使ってプロジェクトを生成してください。  
僕はVSCodeを使っていますが、CubeIDEでも大丈夫だと思います。

https://qiita.com/harashi10969/items/996c183a9993dd1a44fe

# CMakeListsの編集
まず。CMakeLists.txtをC++が使えるように修正します。  
C++のバージョンは17を指定してますが、11とか14とかでも良いと思います。
```diff_c_make
cmake_minimum_required(VERSION 3.22)

#
# This file is generated only once,
# and is not re-generated if converter is called multiple times.
#
# User is free to modify the file as much as necessary
#

# Setup compiler settings
- set(CMAKE_C_STANDARD 11)
- set(CMAKE_C_STANDARD_REQUIRED ON)
- set(CMAKE_C_EXTENSIONS ON)
+ set(CMAKE_CXX_STANDARD 17)
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
+ set(CMAKE_CXX_EXTENSIONS OFF)

# Define the build type
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Debug")
endif()

# Set the project name
set(CMAKE_PROJECT_NAME StandardProjectTest)

# Include toolchain file
include("cmake/gcc-arm-none-eabi.cmake")

# Enable compile command to ease indexing with e.g. clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)

# Enable CMake support for ASM and C languages
- enable_language(C ASM)
+ enable_language(C CXX ASM)

# Core project settings
- project(${CMAKE_PROJECT_NAME})
+ project(${CMAKE_PROJECT_NAME} C CXX ASM)
message("Build type: " ${CMAKE_BUILD_TYPE})

# Create an executable object type
add_executable(${CMAKE_PROJECT_NAME})

# Add STM32CubeMX generated sources
add_subdirectory(cmake/stm32cubemx)

# Link directories setup
target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user defined library search paths
)

# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user sources here
)

# Add include paths
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user defined include paths
)

# Add project symbols (macros)
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user defined symbols
)

# Add linked libraries
target_link_libraries(${CMAKE_PROJECT_NAME}
    stm32cubemx

    # Add user defined libraries
)
```

# C言語からC++コードの呼び出し
CubeMXで生成された全てはC言語で記述されているので、そこからC++コードを呼び出せるようにする必要があります。
この方法はいろいろあると思いますが、その中の一例を示します。今回の方法はSingltonクラスをC言語から呼び出せるようにラッパ関数を用意しました。新しいフォルダ```App```、```App/Inc```、```App/Src```を作成し、その下に```App/Inc/AppMain.hpp```、```App/Inc/AppWrapper.hpp```、```App/Src/AppMain.cpp```、```App/Src/AppMainWrapper.cpp```のファイルを4つ作成します。

### main.h
```diff_cpp
/*
 * 略
 */

/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

/* Private defines -----------------------------------------------------------*/
#define B1_Pin GPIO_PIN_13
#define B1_GPIO_Port GPIOC
#define USART_TX_Pin GPIO_PIN_2
#define USART_TX_GPIO_Port GPIOA
#define USART_RX_Pin GPIO_PIN_3
#define USART_RX_GPIO_Port GPIOA
#define LD2_Pin GPIO_PIN_5
#define LD2_GPIO_Port GPIOA
#define TMS_Pin GPIO_PIN_13
#define TMS_GPIO_Port GPIOA
#define TCK_Pin GPIO_PIN_14
#define TCK_GPIO_Port GPIOA
#define SWO_Pin GPIO_PIN_3
#define SWO_GPIO_Port GPIOB

/* USER CODE BEGIN Private defines */
+ extern UART_HandleTypeDef huart2;  // huart2をAppMain.cpp内で使いたい場合に宣言
/* USER CODE END Private defines */

#ifdef __cplusplus
}
#endif

#endif /* __MAIN_H */

```

### main.c
```diff_cpp
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
+ #include "AppMainWrapper.hpp"
/* 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 ---------------------------------------------------------*/
UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(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_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
+ AppMainWrapper_Init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
+   AppMainWrapper_Update();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
 * 略
 */
```

### CmakeLists.txt
```diff_c_make
#
# 略
# 

# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user sources here
+   App/Src/AppMain.cpp
+   App/Src/AppMainWrapper.cpp
)

# Add include paths
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user defined include paths
+   App/Inc
)

#
# 略
# 
```

### App/Inc/AppMain.hpp (新しくファイル追加)
```cpp
#pragma once

#include <stdint.h>
#include <stm32f4xx_hal.h>

class AppMain
{
    //===DEFINES===//
    //===CONSTANTS===//
private:

    //===VARIABLES===//
private:

    //===FUNCTIONS===//
public:
    static AppMain &GetInstance();
    void Init();
    void Update();

private:
    AppMain();
    ~AppMain();
    AppMain(const AppMain &other);
    AppMain &operator=(const AppMain &other);
};

```

### App/Inc/AppMainWrapper.hpp (新しくファイル追加)
```cpp
#pragma once

#include <stm32f4xx_hal.h>

#ifdef __cplusplus
extern "C"
{
#endif
    void AppMainWrapper_Init(void);
    void AppMainWrapper_Update(void);
#ifdef __cplusplus
}
#endif
```

### App/Src/AppMain.cpp (新しくファイル追加)
```cpp
#include "AppMain.hpp"

#include "main.h"
#include "stm32f4xx_hal.h"

AppMain &AppMain::GetInstance()
{
    static AppMain instance;
    return instance;
}

AppMain::AppMain()
{
}

AppMain::~AppMain()
{
}

void AppMain::Init()
{
   // 初期化処理
}

void AppMain::Update()
{
    // メインループ
}
```

### App/Src/AppMainWrapper.cpp (新しくファイル追加)
```cpp
#include "AppMainWrapper.hpp"

#include "AppMain.hpp"

void AppMainWrapper_Init(void)
{
    AppMain::GetInstance().Init();
}

void AppMainWrapper_Update(void)
{
    AppMain::GetInstance().Update();
}
```

# C++コードの実装
上記の設定ができていれば、C++のコードはAppMainクラス内に記述することができます。
AppMainクラス以外に別のクラスを作成し、AppMainクラスから呼び出すことも可能です。このとき、新しいソースファイル(.cpp)を追加した場合は、CMakeLists.txtにソースファイルを追加するのを忘れないようにしてください。

AppMainクラス内では、以下のようにHALライブラリを使用することも、もちろん可能です。

```cpp
void AppMain::Update()
{
    HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
    HAL_Delay(1000);
}
```
※("NUCLEO-F446RE"のデフォルトピン設定)

# まとめ
今回は自身の備忘録として、STM32CubeMXを用いて生成したプロジェクトで、C++を使用する方法を記事にしました。
同じような作業をする人の参考になれば幸いです。