前回作成したLチカClassをHALに対応させる

前回 https://ioloa.com/?p=157 このページでLチカをしました。

前回はSTM32G4+LLという構成でLチカをしていますが、
STにはLLに非対応なマイコンもあります。

そこで今回は、GPIOCtrl.hppとGPIOCtrl.cppをほんの僅かに変更して、
STM32H7+HALで全ての機能が動くようにします。

以下、既にSTM32H7のプロジェクトがCubeMXを用いて吐かれてる状態から書きます。

①前回のソース
これが前回のソースです。

/*
 * GPIOCtrl.hpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */

#ifndef GPIOCTRL_HPP_
#define GPIOCTRL_HPP_

#include "gpio.h"
#include "stm32g4xx_hal.h"

class GPIOCtrl {
public:
	enum GPIOStatus {
		GPIO_OFF = 0,
		GPIO_ON = 1,
	};

private:
	GPIO_TypeDef *mGPIOx;
	uint32_t mPinMask;
	GPIOStatus mGPIOStatus;
public:
	GPIOCtrl( GPIO_TypeDef *pGPIOx, uint32_t pPinMask );
	virtual ~GPIOCtrl();
	void ON();
	void OFF();
	void Toggle();
};

#endif /* GPIOCTRL_HPP_ */
/*
 * GPIOCtrl.cpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */

#include "GPIOCtrl.hpp"

GPIOCtrl::GPIOCtrl( GPIO_TypeDef *pGPIOx, uint32_t pPinMask)
:mGPIOx(pGPIOx), mPinMask(pPinMask), mGPIOStatus(GPIO_OFF) {
	OFF();
}

GPIOCtrl::~GPIOCtrl() {
}

void GPIOCtrl::OFF() {
	mGPIOStatus = GPIO_OFF;
	LL_GPIO_ResetOutputPin(mGPIOx, mPinMask);
}

void GPIOCtrl::ON() {
	mGPIOStatus = GPIO_ON;
	LL_GPIO_SetOutputPin(mGPIOx, mPinMask);
}

void GPIOCtrl::Toggle() {
	if(mGPIOStatus) {
		OFF();
	} else {
		ON();
	}
}

②次のように変更する
以下のように変更します。

/*
 * GPIOCtrl.hpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */

#ifndef GPIOCTRL_HPP_
#define GPIOCTRL_HPP_

#include "gpio.h"
//マイコンを変更したので利用するライブラリが変わる
//#include "stm32g4xx_hal.h" 
#include "stm32h7xx_hal.h" 

class GPIOCtrl {
public:
	enum GPIOStatus {
		GPIO_OFF = 0,
		GPIO_ON = 1,
	};

private:
	GPIO_TypeDef *mGPIOx;
	uint32_t mPinMask;
	GPIOStatus mGPIOStatus;
public:
	GPIOCtrl( GPIO_TypeDef *pGPIOx, uint32_t pPinMask );
	virtual ~GPIOCtrl();
	void ON();
	void OFF();
	void Toggle();
};

#endif /* GPIOCTRL_HPP_ */
/*
 * GPIOCtrl.cpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */

#include "GPIOCtrl.hpp"

GPIOCtrl::GPIOCtrl( GPIO_TypeDef *pGPIOx, uint32_t pPinMask)
:mGPIOx(pGPIOx), mPinMask(pPinMask), mGPIOStatus(GPIO_OFF) {
	OFF();
}

GPIOCtrl::~GPIOCtrl() {
}

void GPIOCtrl::OFF() {
	mGPIOStatus = GPIO_OFF;
	HAL_GPIO_WritePin(mGPIOx, mPinMask, GPIO_PIN_RESET);
    //LLは用意されていないのでHALを利用する。
	//LL_GPIO_ResetOutputPin(mGPIOx, mPinMask);
}

void GPIOCtrl::ON() {
	mGPIOStatus = GPIO_ON;
	HAL_GPIO_WritePin(mGPIOx, mPinMask, GPIO_PIN_SET);
	//LL_GPIO_SetOutputPin(mGPIOx, mPinMask);
}

void GPIOCtrl::Toggle() {
	if(mGPIOStatus) {
		OFF();
	} else {
		ON();
	}
}

これだけです。
(内容が薄くてすいません。)
このように、システムの中核のClassをすこし変更すれば、
マイコンを変更しても対応できます。

やる気になれば、別のメーカーのマイコンへの移植も容易にできます。

STM32 CubeIDE環境で、C++にて開発する

特にCubeMXなどを弄ることなく、C++を用いて開発するTipsです。

最近発売されたG431のNucleoボードを、C++なソースでLチカすることを目指します。

想定している読者はCubeIDEがある程度扱える中級者です。
時間にして30分くらいの作業かと思います。

以下、執筆時の環境です。

IDE
STM32CubeIDE
Version: 1.0.0
Build: 2872_20190423-2022 (UTC)

マイコン
NUCLEO-G431RB (STM32G431RBT)

準備
今回は、
Fileタブ->New->STM32 Project
から、CubeMXを用いて雛形を生成したところからの話です。

※環境について
ライブラリはGPIO→LLを、RCC→HAL利用しています。
また、PA6を追加でGPIO_Outputとして追加しています。
Firmware Package Name and Version は STM32Cube FW_G4 V1.0.0を利用しています。

CubeMXで雛形を吐き出す再には、(効果があるのか未検証なのですが)
Targeted Language:C++を選択しています。
(これを選んだところでC++ネイティブなソースがCubeMXから出てこないので使えないんですけどね)

また、ペリフェラル等の設定ファイルをmain.cに書かずに、別のファイルに吐き出すオプションもつけています。

これが無いとmainが肥大化して読みにくいです。

①ディレクトリを作る+Pathを通す
C++で開発する以上、1Classに対して1ファイルが一般的なので、標準のInc/Src内に大量にファイルが必ずできます。

まずディレクトリを作ります。
プロジェクトを選択して
Fileタブ->New->Source Folder(フォルダ名MyClass)
MyClassを選択して、
Fileタブ->New->Folder(フォルダ名Inc)
MyClassを選択して、
Fileタブ->New->Folder(フォルダ名Src)
と操作します。
この段階で以下のようになるはずです。

次にPathを通します。
Projectタブ->Properties->C/C++General->Paths and Symbols
で以下のように追加します。

上図では、GNU CのPathを設定していますが、
GNU C及び、GNC C++の両方にPathを通す必要があります。
そのため、GNU C++に対しても同様の設定をしてください。

②Classを作成する
Fileタブ->New->ClassでClassを作成します。

こんな感じで。コンストラクタやデストラクタはお好みで。
コンストラクタとは、オブジェクト生成時に叩かれる関数、
デストラクタとは、オブジェクトがスコープから外れる等の理由で破棄される際に叩かれる関数です。

私の個人的な感想だと、ハードウェアに近い処理ほど、オブジェクトが破棄されることはないため、使わない事が多いです。

生成された後に、然るべき場所にファイルを移動しましょう。

ここではMyClassのIncにヘッダ、Srcにソースファイルを配置しています。
ファイルの移動はドラッグ+ドロップでできます。

今回のClassはGPIOの制御なので、以下のように記述しました。

/*
 * GPIOCtrl.hpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */

#ifndef GPIOCTRL_HPP_
#define GPIOCTRL_HPP_

#include "gpio.h"
#include "stm32g4xx_hal.h"

class GPIOCtrl {
public:
	enum GPIOStatus {
		GPIO_OFF = 0,
		GPIO_ON = 1,
	};

private:
	GPIO_TypeDef *mGPIOx;
	uint32_t mPinMask;
	GPIOStatus mGPIOStatus;
public:
	GPIOCtrl( GPIO_TypeDef *pGPIOx, uint32_t pPinMask );
	virtual ~GPIOCtrl();
	void ON();
	void OFF();
	void Toggle();
};

#endif /* GPIOCTRL_HPP_ */
/*
 * GPIOCtrl.cpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */

#include "GPIOCtrl.hpp"

GPIOCtrl::GPIOCtrl( GPIO_TypeDef *pGPIOx, uint32_t pPinMask)
:mGPIOx(pGPIOx), mPinMask(pPinMask), mGPIOStatus(GPIO_OFF) {
	OFF();
}

GPIOCtrl::~GPIOCtrl() {
}

void GPIOCtrl::OFF() {
	mGPIOStatus = GPIO_OFF;
	LL_GPIO_ResetOutputPin(mGPIOx, mPinMask);
}

void GPIOCtrl::ON() {
	mGPIOStatus = GPIO_ON;
	LL_GPIO_SetOutputPin(mGPIOx, mPinMask);
}

void GPIOCtrl::Toggle() {
	if(mGPIOStatus) {
		OFF();
	} else {
		ON();
	}
}

C++の中身については、「C++の本読んで」とか、「ツヨツヨな友達に聞いて」とか、「C++扱う業種に転職して体で覚える」とか、「Twitterでリプください」などと乱暴な事しか言えないのですが、ほんの少しキーワードを置いておきますと、
:mGPIOx(pGPIOx), mPinMask(pPinMask), mGPIOStatus(GPIO_OFF)
はメンバイニシャライザです。
関数内での代入よりちょっと早いです。
参考:
http://jagabeeinitialize.hatenablog.com/entry/2018/01/21/192043
マイコンと相性が良いですね。

このClassは
GPIOCtrl myIO(GPIOA, GPIO_PIN_5);
myIO.ON();
みたいな感じで使うことを想定して書きました。

続いて神クラスを作ります。普通はタブーなのですが、
ちょっとした理由で必要なんです。
同様の要領で以下のように作ってください。

Godだと思った?残念、Deusでした。

さて、DeusClassは以下のように実装してください。

/*
 * Deus.hpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */

#ifndef INC_DEUS_HPP_
#define INC_DEUS_HPP_

#include "GPIOCtrl.hpp"

class Deus {
public:
	Deus();
	virtual ~Deus();
	void Ctrl();
};

#endif /* INC_DEUS_HPP_ */
/*
 * Deus.cpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */

#include "Deus.hpp"

Deus::Deus() {
	// TODO Auto-generated constructor stub

}

Deus::~Deus() {
	// TODO Auto-generated destructor stub
}

void Deus::Ctrl() {
	GPIOCtrl myGPIO1(GPIOA, GPIO_PIN_5);
	GPIOCtrl myGPIO2(GPIOA, GPIO_PIN_6);

	myGPIO1.ON();
	myGPIO2.OFF();

	while(1){
		myGPIO1.Toggle();
		myGPIO2.Toggle();
		HAL_Delay(1000);
	}
}

③Wrapperを作成する
やっとこれらをMainに読ませます。
ただし、直接Mainに読ませてはいけません。
Wrapperファイルを作ります。以下のように配置してください。

/*
 * Wrapper.hpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */

#ifndef WRAPPER_HPP_
#define WRAPPER_HPP_

#ifdef __cplusplus
extern "C" {
#endif

void cppWrapper(void);

#ifdef __cplusplus
};
#endif


#endif /* APPLICATION_USER_WRAPPER_HPP_ */
/*
 * Wrapper.cpp
 *
 *  Created on: Sep 8, 2019
 *      Author: Nakamura(Yutaka)
 */
//このClassだけは、C/C++の架け橋なので、
//.hppの#includeがHedder側でできない。
//なのでソース内に記述する。

#include "Wrapper.hpp"
#include "Deus.hpp"



Deus Bosatsu;


void cppWrapper(void){

	Bosatsu.Ctrl();

}

わざわざbosatsuを作った理由は、WrapperではhppのIncludeができないので、C++の記述ができず、例えばbool型などが扱えません。非常に使いづらいので、これを回避するためだけに2重にWrapしているイメージです。

③Mainに記述する
ようやくmain.cに記述します。

/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

#include "Wrapper.hpp"
/* Private includes ----------------------------------------------------------*/

中略

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  cppWrapper();
  /* USER CODE END 2 */

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

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

④Debugする
これでようやく完成です。長かったですね。たかがLチカなのに!

お疲れさまでした。
面倒臭かったですよね?ここまでコストを払ってまでC++で開発するメリットは勿論あって、Cに比べてカプセル化が容易です。

Classに状態と動作を記録し複製できるので、
例えばPID制御や微分演算子、積分演算子等は1~2周期前の状態と、動作を記録するので、Cでは面倒臭い or ソースを汚す手法でしか、(例えば積分演算子を)複製できませんが、C++ならばClassのオブジェクトとして複製できます。

もちろんデメリットもあり、それはソースが非オブジェクト指向の言語に比べて肥大化しやすい→技術的負債が発生することです。

つまり、 バカが作るとゴミの山が発生する ということです。

これを回避するために、プログラマたちは、テスト駆動開発などの、管理しやすいソースを記述する方法が考案されていたりします。

いつかはそのあたりの話も書きたいと思います。