はじめに
STM32CubeMXはGUIで利用するペリフェラルの設定を行い、初期化コードを自動的に生成してくれるツールです。このツールは便利なのですが、現時点ではC++のコードを生成することはできません。
しかし、C++でアプリケーションを記述することは不可能ではありません。方法はいくつかあると思いますが、この記事ではラッパ関数を作成してC++のコードを利用する方法をご紹介します。
環境
-
OS: macOS Sierra 10.12.3
-
IDE: Eclipse IDE for C/C++ Developers 4.6.2
-
ツールチェイン: SW4STM32 - Eclipseのプラグインとしてインストール
-
CubeMX: 4.20.0.201703031029 - Eclipseのプラグインとしてインストール
-
OpenOCD: 1.13.0.201701121612
-
マイコンボード: NUCLEO-F411RE
プロジェクトの生成
まずはCubeMXでプロジェクトの生成を行います。プロジェクトの生成はCで開発する際と同様に行います。この記事では、ピンアサインについてはNUCLEO-F411REの初期設定のままでプロジェクトを生成し、LD2(オンボードLED)をLチカしてみることにします。
CubeMXのコード生成オプションは次の赤枠内の部分はデフォルトから変更してあります(C++を使うことに直接関係があるわけではないのですが、この設定にしないとコードチェックの際に不要なエラーがEclipseによって出力されるようなので変更しています)。
C++プロジェクトに変換する
SW4STM32の機能で、「C++プロジェクトに変換する」というものがあります。この機能の利用は簡単です。まず、EclipseのProject Explorerから現在のプロジェクトを探し、その上で右クリックします。そして、出てきたコンテキストメニューの中からConvert to C++というものをクリックするだけです。これにより、C++のコードもコンパイルできるようになるようです。
Lチカするクラスを作成する
プロジェクト内のApplication/Userの上で右クリックし、New->Classから、Userディレクトリ内にクラスを新規作成します。クラス名は適当に決めます。記事のサンプルを作成する際はLedBlinkにしました。namespaceはなしでも大丈夫です。ヘッダのファイル名は、CとC++のファイルが混在するプロジェクトになることを考慮して、C++のファイルであることを明示するために拡張子をhppにしてみました。
生成されたクラスにLEDのOn/Offを切り替えるメソッドを追加してみます。
#ifndef APPLICATION_USER_LEDBLINK_HPP_
#define APPLICATION_USER_LEDBLINK_HPP_
class LedBlink {
public:
void toggle();
};
#endif /* APPLICATION_USER_LEDBLINK_HPP_ */
LD2ピンの出力を反転し、100ms待つだけのメソッドです。HALライブラリのヘッダを読み込めば、HALの関数はそのまま使えました。
#include "LedBlink.hpp"
#include "stm32f4xx_hal.h"
void LedBlink::toggle() {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(100);
}
ラッパ関数を作る
main.cから呼び出すための関数を作成します。Cのコード内でクラスのインスタンスを生成して利用することはできないため、C++のコード内で関数を定義し、その中でクラスを利用します。しかし、C++の関数をそのままCのソースコード内で呼び出そうとすると、リンクの際にエラーが発生します。1これを回避するために次の仕掛けを使います。
extern "C"
言語リンケージというものを使用すると、Cから呼び出し可能な関数を定義することができます。具体的には、ヘッダファイル内で、次のような記法で関数の宣言を行います。これによって、C++のソースコード内でもextern "C" {
から}
までの部分は、Cのソースコードとして解釈されるようになります。2
extern "C" {
// 関数宣言
};
#ifdef __cplusplus
__cplusplus
はC++としてコンパイルされると定義されるマクロです。Cとしてコンパイルされている時にCのソースコードとして解釈するよう命令することはできない(意味がない)ので、このマクロを利用してC++としてコンパイルされた場合にのみextern "C"
が指定されるようにします。
ラッパ関数の実装
まずヘッダファイルを作ります。Application/User以下に、C++のヘッダファイルを追加します。
追加したら、次のように記述します。ここでは、main.cのwhile文の中から呼び出すためのcpploop
という関数を宣言しました。
#ifndef APPLICATION_USER_WRAPPER_HPP_
#define APPLICATION_USER_WRAPPER_HPP_
#ifdef __cplusplus
extern "C" {
#endif
void cpploop(void);
#ifdef __cplusplus
};
#endif
#endif /* APPLICATION_USER_WRAPPER_HPP_ */
続いて、C++のソースファイルを生成し、関数の中身を次のように実装します。関数cpploop
はCの関数として呼び出し可能となっていますが、このソースコード自体はC++であるため、ここではC++の機能を利用することができます。
#include "wrapper.hpp"
#include "LedBlink.hpp"
void cpploop(void) {
LedBlink instance;
instance.toggle();
}
main.cから呼び出す
まず、先ほど作ったwrapper.hppをmain.cから読み込みます。38行目あたりのユーザ用のincludeを記述する部分に次のように記述します。Project Exploer上から見ると一見main.cと自分で作ったwrapper.hppは同じディレクトリにあるように見えるのですが、実際にはmain.cはショートカットになっておりApplication/Userディレクトリにはありません。このため次のように指定しないといけないようです。
38: /* USER CODE BEGIN Includes */
39: #include "../SW4STM32/wrapper_example/Application/User/wrapper.hpp"
そして、while文(91行目あたり)の中から先ほど定義した関数を呼び出します。
91: /* USER CODE BEGIN 3 */
92: cpploop();
動作確認
ビルドと書き込みは通常のプロジェクトと同様に行うことができるためここでは省略します。
おわりに
この記事ではCubeMXで生成したコードを活かしつつC++でアプリケーションを開発する方法をご紹介しました。紹介した方法はCubeMXが生成したファイルにできるだけ手をつけない方法となっているため、コードを再生成した際の手間なども少なくなっているのではないかと思います。
筆者はSTM32マイコンに触り始めたばかりであるため、間違った情報も含まれているかもしれません。誤りを発見した際はコメント等でお気軽にお知らせいただけるとありがたいです。
-
C++で関数を定義した場合は、namespaceやオーバーロードなどの機能を実現するため関数のシンボル名がCの場合とは異なったものになってしまうためです。(https://www.ibm.com/support/knowledgecenter/ja/ssw_ibm_i_73/rzarg/name_mangling.htm) ↩
-
https://docs.oracle.com/cd/E19957-01/805-7888/i990454/index.html ↩