2024 TRONプログラミングコンテスト 開発環境・開発ツール部門 応募プログラム【NT-Shell・CppUTestを使用した簡易テスト環境】の紹介

# この記事は何?
**2024 TRONプログラミングコンテスト**の**開発環境・開発ツール部門**に**NT-Shell・CppUTestを使用した簡易テスト環境**をテーマに応募しました。

この記事は**NT-Shell・CppUTestを使用した簡易テスト環境**について説明します。


# コンテスト概要
TRONプログラミングコンテストについての説明はこちらの公式ホームページの説明を引用させていただきます。

2024 TRONプログラミングコンテスト 公式ホームページ

https://www.tron.org/ja/programming_contest/

>世界標準であるTRONのリアルタイムOS「μT-Kernel 3.0」と各社の最新のマイコンを用いたアプリケーション、 ミドルウェア、開発環境、そしてツールなど各分野のプログラムを募集します。リアルタイム性、省電力、小フットプリント などリアルタイムOSの特性を引き出すテクノロジー、新しい技術を活用した開発環境やツールなどを競います。
応募者には選考の上、協力各社より提供頂いたμT-Kernel 3.0を搭載した最新のマイコンボードが提供されます。

## マイコンボード
コンテストに使用するマイコンボードは下図より選択可能です（[TRONプログラミングコンテスト 公式ホームページ](https://www.tron.org/ja/programming_contest/)より引用）。

![board.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/a8d3bafe-6c37-da2a-fcf1-70708a97d754.jpeg)

私はSTマイクロエレクトロニクス株式会社の評価ボード**Nucleo-H723ZG**を選択しました。
この評価ボードを選択した理由はSTマイクロエレクトロニクスの評価ボードは過去に使ったことがあるからです。

## コンテストの応募部門
コンテストは以下の3つの応募部門があります。

1. RTOSアプリケーション部門
2. RTOSミドルウェア部門
3. 開発環境・開発ツール部門

私は**開発環境・開発ツール部門**することに決めました。

# コンテスト応募背景・理由
社会人になり仕事ではじめて使ったRTOSがμITRON準拠のTOPPERS/JSPでした。いままで不思議と（?）μT-Kernelはいままでつかったことがありませんでした。
Webを見ていて偶然コンテストの存在を知り、応募してみることにしました。

あとは20年くらい前にプロジェクトXを見て、トロンのことを知ってから坂村先生、トロンのファンになったから、というのも根底にあると思います。RTOSといえばμITRON準拠のものというのが自分の中に染み付いています。

> 新価格版 プロジェクトX 挑戦者たち 家電革命 トロンの衝撃

https://www.nhk-ep.com/products/detail/h16477AA

所有DVDのパッケージを撮影（表）
![project_x_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/0210dd67-2dce-33ec-7cee-d727c032f570.jpeg)

所有DVDのパッケージを撮影（裏）
![project_x_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/a79807af-36d9-0254-29b6-0a14b14bb921.jpeg)

# テーマ選定の背景・理由
今回は**NT-Shell・CppUTestを使用した簡易テスト環境**をテーマに開発環境・開発ツール部門に応募しました。
このテーマにした背景・理由についてです。

## 開発者の不安を軽減したい
組込みソフトウェア開発のテストでは、

* ハードウェア非依存部、依存部を明確にした分離した設計を行う
* ハードウェア非依存部はホストPCでTDD開発環境を構築し、アプリケーションロジックを確認する。

を目指すのが理想だと思います。そうすることで何か問題は起こった時にハードウェア起因か否かの判断もつきやすいと思います。

とは言っても綺麗にハードウェア依存部、非依存部を分離した構造にできないこともあるかと思います。

綺麗にハードウェア依存部、非依存部を分離できたとしても「実機で動かしていない」は開発者にとって不安な点だと思います。

今回のNT-Shell・CppUTestを使用した簡易テスト環境は**気軽に実機環境でテストができることで開発者の不安軽減につなげたい**という思いで作りました。

## 多様な観点のテストで不安を少なくし、自分のコードに自信を持つ
テストは通常、組込みソフトウェアのアプリケーションロジックの確認を目的に作成、実行することがメインだと思います。
ただ、ソフトウェア開発のプロセスにおいて開発者の不安はアプリケーションロジックの動作以外にも存在すると思います。

例えば以下があると思います。

* プログラミング言語がわからず不安
* 使用するマイコンアーキテクチャの挙動がわからず不安
* 使用するマイコンの仕様がわからず不安
* ライブラリの実装状態がわからず不安
* そもそもテストフレームワークの使い方がわからず不安
* 実行基盤であるRTOS（今回の場合、μT-Kernel）の使い方がわからず不安

こういった数々の不安をテストを使い、確認し学んでいくことで軽くできないかと思いました。
NT-Shell・CppUTestを使用した簡易テスト環境は上記を確認するテストを書き確認をおこないました。


# 開発環境
NT-Shell・CppUTestを使用した簡易テスト環境の開発環境です。

## ホストPC
* MacBook Retina 12-inch, 2017
* プロセッサ: 1.2 GHz デュアルコア Intel Core m3
* SSD 250G
* メモリ: 8GB, LPDDR3

## ハードウェア
* STマイクロエレクトロニクス株式会社 Nucleo-H723ZG
* マイコン： STM32H723
* CPUコア： Arm®️ Cortex®️-M7
* 550MHz
* Flash memory：1 Mbyte 
* SRAM：564 Kbyte 

## ソフトウェア
### STM32Cube IDE
STM32CubeIDE Version: 1.13.2
Build: 18220_20230914_1601 (UTC)

### NTShell
Version 0.3.1

### CppUTest
v3.8


# 開発環境構築手順
## STM32CubeIDEのインストール
STM32CubeIDEをインストールします。

## 簡易テスト環境のクローン
簡易テスト環境をGitHubから任意のディレクトリにクローンします。
実行コマンドは下記です。

```
$ git clone git@github.com:grace2riku/mtk3_bsp2_easy_test_env.git
```

実行結果は下記です。

```
Cloning into 'mtk3_bsp2_easy_test_env'...
remote: Enumerating objects: 1318, done.
remote: Counting objects: 100% (1318/1318), done.
remote: Compressing objects: 100% (673/673), done.
remote: Total 1318 (delta 561), reused 1271 (delta 516), pack-reused 0 (from 0)
Receiving objects: 100% (1318/1318), 1.69 MiB | 1010.00 KiB/s, done.
Resolving deltas: 100% (561/561), done.
```

## CppUTestのクローン
CppUTest v3.8をSTM32CubeIDEのプロジェクトでライブラリにしました。
こちらもGitHubから任意のディレクトリにクローンします。
実行コマンドは下記です。

```
$ git clone -b v3.8 git@github.com:grace2riku/cpputest.git
```

実行結果は下記です。

```
Cloning into 'cpputest'...
remote: Enumerating objects: 314, done.
remote: Counting objects: 100% (314/314), done.
remote: Compressing objects: 100% (232/232), done.
remote: Total 314 (delta 76), reused 311 (delta 76), pack-reused 0 (from 0)
Receiving objects: 100% (314/314), 2.59 MiB | 1.00 MiB/s, done.
Resolving deltas: 100% (76/76), done.
```

## STM32CubeIDEの操作
ここからはSTM32CubeIDEの操作です。

### STM32CubeIDEの起動
STM32CubeIDEを起動します。
ワークスペースのパスを確認し、Launchボタンを押します。

![stm32cubeide_start.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/97d26666-7810-14af-37b7-ac6b2db519b5.jpeg)

### 簡易テスト環境のインポート
先ほどGitHubからクローンした簡易テスト環境をインポートします。
FileメニューからImportを選択します。

![stm32cubeide_file-import.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/5c45afa6-1994-edba-4e97-8414853490d3.jpeg)

Existing Projects into Workspaceを選択します。
![ide_existing_project_into_workspace.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/927f92bb-15e0-7355-2086-15ce7e8a86c9.jpeg)

Browseボタンを押下し、簡易テスト環境のディレクトリ（mtk3_bsp2_easy_test_env）を選択します。
![ide_import_select_mtk3-bsp2_prj_dir.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/258baf1d-e5ea-931f-d0b0-4b195e257689.jpeg)

Projects:にmtk3_bsp2_easy_test_envがチェックされていることを確認し、Finishボタンを押します。
![ide_import_mtk3-bsp2_prj.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/47009ba2-6b66-3451-d61a-9ef0438a3ed1.jpeg)

STM32CubeIDEの左側にmtk3_bsp2_easy_test_envのプロジェクトがインポートされます。
![ide_import_complete_mtk3-bsp2_prj.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/c4f94723-6a6e-6166-ba74-096371a87cdc.jpeg)

### CppUTestのインポート
簡易テスト環境のインポート手順と同じようにCppUTestのプロジェクトのディレクトリ（cpputest）を選択します。
![ide_import_select_cpputest_prj_dir.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/5367786e-501e-3e14-b803-9d3c1e879850.jpeg)

Projects:にcpputestがチェックされていることを確認し、Finishボタンを押します。
![ide_import_cpputest_prj.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/f00d4ea9-9975-a5d5-e21c-a237491ea359.jpeg)

STM32CubeIDEの左側にcpputestのプロジェクトがインポートされます。
![ide_import_complete_cpputest_prj.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/d914cd69-2ed4-769b-8ae6-bfec93a14b80.jpeg)

### CppUTestのビルド
CppUTestのプロジェクトをビルドし、ライブラリを作成します。
ライブラリは簡易テスト環境のプロジェクトでリンクしているのでCppUTestのプロジェクトを先にビルドします。
ビルドアイコンを押します。

![ide_build-cpputest-lib.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/34033e71-af7f-936f-494b-eb1c31451597.jpeg)

ビルドが完了するとConsoleのタブにcreating libcpputest.aと表示されます。
![ide_create-cpputest-lib.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/1d924c5a-0359-84a5-a76f-7a52b16f9472.jpeg)

### 簡易テスト環境のビルド
簡易テスト環境のプロジェクトをビルドします。
簡易テスト環境は先にビルドしできたCppUTestのライブラリをリンクしています。
ビルドアイコンを押します。

![ide_build-mtk3-bsp2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/b13edbc5-8bab-2368-7c9a-4d5e64776691.jpeg)

ビルドが完了するとConsoleのタブにFinished building target: mtk3_bsp3_easy_test_env.elfと表示されます。
![ide_create-elf.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/ee15f339-ba82-6348-ddd5-10d26818761c.jpeg)

### デバッグ
ビルドが完了したらPCと評価ボードのUSBコネクタ（CN1）をUSBケーブル（A-マイクロB）で接続します。
デバッグボタンを押し、先ほど作成したmtk3_bsp3_easy_test_env.elfを評価ボードにダウンロードします。

![ide_debug_start.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/dc26abf9-542a-5692-ee00-e8f1b9186652.jpeg)

デバッグの準備が完了するとmain関数の先頭で停止します。

![ide_debug_connect.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/2af169db-8639-af42-f323-fe523d3d9247.jpeg)

Resumeボタンを押すとプログラムをスタートします。

![ide_debug_resume.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/08d26e19-88e1-cc21-e15d-d7de811e141c.jpeg)


## シリアル通信の接続
任意のシリアルコンソールでシリアル通信に接続します。
この記事ではminicomを使います。

シリアルのパス名を確認します。

```
$ ls /dev/cu.usb*
/dev/cu.usbmodem14103
```

minicomでボーレート115200bpsでシリアル通信に接続します。
ボーレート以外の通信条件はつぎのとおりです。
* データ長：8bit
* パリティ：なし
* ストップビット：1bit
* フロー制御：なし

```
$ minicom -D /dev/cu.usbmodem14103 -b 115200
```

接続できるとつぎの表示になります。

```
Welcome to minicom 2.8

OPTIONS:
Compiled on Jan  4 2021, 00:04:46.
Port /dev/cu.usbmodem14103, 08:15:22

Press Meta-Z for help on special keys
```

## シリアル通信の接続確認
シリアル通信で接続できたらソフトウェアが応答するかコマンド実行し確認してみます。
シリアルコンソールでエンターキーを押下するとキャレット（>）が表示されます。

```
>
```

helpを押下しエンターキーを押下します。

```
>help
help    :This is a description text string for help command.
info    :This is a description text string for info command.
read_userbutton :User button B1 reads.
write_led1      :Write to LED LD1.
write_led2      :Write to LED LD2.
write_led3      :Write to LED LD3.
cpputest        :Exec CppUTest.
```

NT-Shell・CppUTestを使用した簡易テスト環境が実行できるコマンドとコマンドの説明が表示されます。
これが確認できればシリアルコンソールと評価ボードの間の接続は問題ありません。

## シリアルコンソールのキャリッジリターン設定
helpコマンドで表示されたcpputestコマンドを綺麗に表示するためにキャリッジリターンを設定します。
minicomの場合、EscキーとZキーを押下すると表示される下図の画面でUキーを押します。

![add_cr.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/c859de1a-187f-3ae8-7740-80d4f761deaf.jpeg)

# 機能説明
NT-Shell・CppUTestを使用した簡易テスト環境の機能について説明します。

## NT-Shell
記事の冒頭でつぎの文言を書きました。

> 今回のNT-Shell・CppUTestを使用した簡易テスト環境は**気軽に実機環境でテストができることで開発者の不安軽減につなげたい**という思いで作りました。

この**気軽に実機環境でテストができること**を実現する要素技術としてNT-Shellを選択しました。
NT-Shellは組込みシステム用のシェルです。NT-Shellを組込むことでシリアルコンソールから以下の操作が可能になります。

* カーソルキーによる移動
* Backspace, Deleteキー押下による文字の削除
* 上下キー押下による実行コマンドの履歴参照
* Ctrl+aで行の先頭、Ctrl+eで末尾への移動

これらの操作がシリアルコンソールからできることでデバッグしやすい、気軽に実機環境でテストできることにつながります。


## コマンド　
NT-Shell・CppUTestを使用した簡易テスト環境ではいくつかコマンドを定義しています。
シリアルコンソールからhelpを押下し、表示される文字列がコマンドです。

```
>help
help    :This is a description text string for help command.
info    :This is a description text string for info command.
read_userbutton :User button B1 reads.
write_led1      :Write to LED LD1.
write_led2      :Write to LED LD2.
write_led3      :Write to LED LD3.
cpputest        :Exec CppUTest.
```

コマンドをいくつか紹介します。

### CppUTest
シリアルコンソールからcpptestと押下するとテストフレームワークCppUTestで書かれたテストを実行します。

以下の例はcpputestを実行した場合の表示です。
この表示は以下を表しています。

* 39個のテストがある
* 36個がテストOK
* 68個のチェック項目がある
* 3つのテストを無視（テストを実行せずにスキップ）した

```
>cpputest
.....!!.......!........................
OK (39 tests, 36 ran, 68 checks, 3 ignored, 0 filtered out, 0 ms)
```

CppUTestには実行オプションがあります。

詳しくはこちらのCommand line Switchesを参照ください。

https://cpputest.github.io/manual.html#command_line

以下は-vを指定した場合の表示です。
オプションなしの実行に比べて、実行しているテストが詳細に表示されるようになりました。

```
>cpputest -v
TEST(TimeManagementTestGroup, BasicUsage_tk_get_otm) - 0 ms
TEST(TimeManagementTestGroup, BasicUsage_tk_get_tim) - 0 ms
TEST(SyncAndComTestGroup, BasicUsageMailbox) - 0 ms
TEST(SyncAndComTestGroup, MailboxUnknownCommandReceived) - 0 ms
TEST(MtKernelTaskManegementLerningTestGroup, BasicUsage_tk_ref_tsk) - 0 ms
IGNORE_TEST(MtKernelApiLerningTestGroup, CreateAndStartTsk) - 0 ms
IGNORE_TEST(IOPortAccessSupportTestGroup, in_w_B1USER_Button_Pressed) - 0 ms
TEST(IOPortAccessSupportTestGroup, in_w_B1USER_Button_NotPressed) - 0 ms
TEST(IOPortAccessSupportTestGroup, out_w_and_in_w) - 0 ms
TEST(FutureProblemTestGroup, 2038problem) - 0 ms
TEST(FutureProblemTestGroup, pre2038problem) - 0 ms
TEST(CPUSpecificationsGroup, endian) - 0 ms
TEST(CpputestUsageGroup, setup) - 0 ms
TEST(CpputestUsageGroup_2, setup) - 0 ms
IGNORE_TEST(CpputestUsageGroup, setupOtherTestGroupVariableReference) - 0 ms
TEST(CpputestUsageGroup, setupGlobalVariableReference) - 0 ms
TEST(CpputestUsageGroup_2, setupGlobalVariableReference) - 0 ms
TEST(CpputestUsageGroup, BasicUsage_BITS_EQUAL_Assertions) - 0 ms
TEST(CppLerningTestGroup, defaultArgument_arg1_2) - 0 ms
TEST(CppLerningTestGroup, defaultArgument_arg2) - 0 ms
TEST(CppLerningTestGroup, defaultArgumentNone) - 0 ms
TEST(CppLerningTestGroup, BasicUsageBinaryLiterals) - 0 ms
TEST(CppLerningTestGroup, BasicUsageDigitSeparators) - 0 ms
TEST(CortexM7ArchitectureGroup, intSize) - 0 ms
TEST(MinusTestGroup, Minus_MinValueInput) - 0 ms
TEST(MinusTestGroup, Minus_MaxValueInput) - 0 ms
TEST(MinusTestGroup, Minus_LargeNegativeDifference) - 0 ms
TEST(MinusTestGroup, Minus_LargePositiveDifference) - 0 ms
TEST(MinusTestGroup, Minus_SameValues) - 0 ms
TEST(MinusTestGroup, Minus_BoundaryCondition_AboveMin) - 0 ms
TEST(MinusTestGroup, Minus_BoundaryCondition_BelowMin) - 0 ms
TEST(MinusTestGroup, Minus_BoundaryCondition_ExactMin) - 0 ms
TEST(MinusTestGroup, Minus_ValidInput_WithinBounds) - 0 ms
TEST(MinusC2CoverageTestGroup, Minus_MinLessThanAns) - 0 ms
TEST(MinusC2CoverageTestGroup, Minus_MinGreaterThanAns) - 0 ms
TEST(MinusC2CoverageTestGroup, Minus_MinLessThanData2) - 0 ms
TEST(MinusC2CoverageTestGroup, Minus_MinGreaterThanData2) - 0 ms
TEST(MinusC2CoverageTestGroup, Minus_MinLessThanData1) - 0 ms
TEST(MinusC2CoverageTestGroup, Minus_MinGreaterThanData1) - 0 ms

OK (39 tests, 36 ran, 68 checks, 3 ignored, 0 filtered out, 0 ms)
```

もうひとつオプションを指定したテストの実行例を示します。
-sgはテストグループのテストを実行するオプションです。
以下の例はTimeManagementTestGroupのテストのみを実行してます。

```
>cpputest -v -sg TimeManagementTestGroup
TEST(TimeManagementTestGroup, BasicUsage_tk_get_otm) - 0 ms
TEST(TimeManagementTestGroup, BasicUsage_tk_get_tim) - 0 ms

OK (39 tests, 2 ran, 2 checks, 0 ignored, 37 filtered out, 0 ms)
```

さきほどはテストグループのテストを実行した例でした。
テスト名で実行するテストを指定し、テストを実行することもできます。
その場合は-snオプション、実行したいテスト名を指定します。
以下の例ではBasicUsageMailboxテストを指定しています。

```
>cpputest -v -sn BasicUsageMailbox
TEST(SyncAndComTestGroup, BasicUsageMailbox) - 0 ms

OK (39 tests, 1 ran, 2 checks, 0 ignored, 38 filtered out, 0 ms)
```

このようにシリアルコンソールから全てまたは任意のテストを実行できます。

### LED点灯・消灯
write_led*（* = 1, 2, 3）コマンドはLEDを点灯・消灯するコマンドです。
つぎのコマンドでLED LD1を点灯します。

```
>write_led1 1
```

つぎのコマンドでLED LD1を消灯します。
```
>write_led1 0
```

LED2（LD2）, LED3（LD3）もLED1（LD1）と同様に点灯、消灯できます。

### スイッチ読込み
read_userbuttonコマンドはUSER B1ボタンのレベルを読込みます。
USER B1ボタンを押していないときは0が読み出せます。

```
>read_userbutton
USER BUTTON B1 = 0
```

USER B1ボタンを押しているときは1が読み出せます。

```
>read_userbutton
USER BUTTON B1 = 1
```


# プログラム説明
## プログラム構成
NT-Shell・CppUTestを使用した簡易テスト環境のプログラム構成です。

### 簡易テスト環境のプロジェクト
下図が簡易テスト環境のプロジェクトの構成です。
画像で選択されている項目がSTM32CubeIDEのデフォルト状態から変更した項目です。

![mtk3_bsp2_prj_tree.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/d8d11822-83ce-23b4-f483-977a86490415.jpeg)

下表は変更した項目の一覧表です。

| パス | ファイル名 | 内容 | 備考 |
| - | - | - | - |
| Application | app_main.c | μT-Kernel3.0が呼び出すユーザプログラムのusermain関数を定義している。システムで使うタスク、資源を生成している。自身はtk_slp_tskでスリープし、他のタスクの処理に移行する。 |  |
| Application | led_blink.c | LED3（LD3）の点滅を行うタスク |  |
| Application | ntshell_task.c | NT-Shellタスク。シリアルコンソールからの文字列入力・出力する。シリアルコンソールの入力文字列がコマンド文字列に一致する場合はusercmd.cのコマンドを実行する。 |  |
| Application | usercmd.c | ユーザーコマンドの実装。 |  |
| Core/Src | main.c | STM32CubeIDEが自動生成するmain関数。μT-Kernel 3.0の起動処理knl_start_mtkernel関数を実行するようにコードを追加している。 | [参考URL: 4.2.3. OS起動処理の呼び出し](https://github.com/tron-forum/mtk3_bsp2/blob/main/doc/bsp2_stm32_cube_jp.md#423-os%E8%B5%B7%E5%8B%95%E5%87%A6%E7%90%86%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97) |
| lib/nt-shell/core |  | NT-Shell Core モジュール | [参考URL: NT-Shell　Architecture](https://cubeatsystems.com/ntshell/architecture.html) |
| lib/nt-shell/util |  | NT-Shell Util モジュール | [参考URL: NT-Shell　Architecture](https://cubeatsystems.com/ntshell/architecture.html) |
| mtk3_bsp2 |  | [μT-Kernel 3.0 BSP2](https://github.com/tron-forum/mtk3_bsp2) | mtkernelディレクトリは[μT-Kernel 3.0](https://github.com/tron-forum/mtkernel_3) |
| test/calclogic_ChatGPT_support | calclogic_ChatGPT_support.cpp | アプリケーションロジックのテスト（テストをChatGPTで生成） | [参考リンク：アプリケーションロジックのテストをChatGPTで生成した事例](#アプリケーションロジックのテストをchatgptで生成した事例) |
| test/cortexM7_architecture | cortexM7_architecture_test.cpp | Cortex-M7のアーキテクチャを確認するテスト | [参考リンク：マイコンアーキテクチャ（Cortex-M7）のテスト](#マイコンアーキテクチャcortex-m7のテスト) |
| test/cpp_lerning_test | cpp_lerning_test.cpp | C++を学習するテスト | [参考リンク：C++の学習テスト](#cの学習テスト) |
| test/cpputest_usage | cpputest_usage.cpp | テストフレームワークCppUTestの使い方を学べるテスト | [参考リンク：テストフレームワークCppUTestの学習テスト](#テストフレームワークcpputestの学習テスト) |
| test/cpu_specifications | cpu_specifications_test.cpp | ターゲットマイコンのエンディアンのテスト | [参考リンク：ターゲットマイコンの仕様を確認するテスト](#ターゲットマイコンの仕様を確認するテスト) |
| test/future_problem_test | future_problem_test.cpp | 2038年問題のテスト | [参考リンク：将来の問題のテスト](#将来の問題のテスト) |
| test/mtkernel_api_lerning_test |  | μT-Kernel 3.0のAPIを学習するテスト | [参考リンク：μT-Kernel 3.0の学習テスト](#μt-kernel-30の学習テスト) |

変更の方針はつぎにしました。

* ApplicationディレクトリにNT-Shell・CppUTestを使用した簡易テスト環境のユーザープログラムを格納（STM32CubeIDEが生成したファイルは格納していない）
* libディレクトリにNT-Shellのディレクトリをつくり、その中にNT-Shellのライブラリを格納
* mtk3_bsp2ディレクトリにBSP2（μT-Kernel 3.0 BSP2）、μT-Kernel（μT-Kernel 3.0）を格納（環境構築は[GitHubのドキュメントを参照](https://github.com/tron-forum/mtk3_bsp2/blob/main/doc/bsp2_stm32_cube_jp.md#4-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E4%BD%9C%E6%88%90%E6%89%8B%E9%A0%86)した）
* testディレクトリ以下にさらにテスト観点ごとにディレクトリをつくりテストコードを格納

### CppUTestのプロジェクト
テストフレームワークCppUTestはライブラリ化します。
STM32CubeIDEのメニューからライブラリのプロジェクトとして作成します。

こちらの記事とおりの手順で進めました。

>LowLevelCode
>Unit Testing in STM32CubeIDE

https://lowlevelcode.com/unit-testing-in-stm32cubeide/

CppUTestのソースコードはこちらのページからダウンロードしたv3.8を使いました。

> ./ Cpputest
> Download Release 3.8 as .zip 

https://cpputest.github.io/


解凍したファイルの中から以下のディレクトリ・ファイルをSTM32CubeIDEのプロジェクトにコピーしました。

* cpputest-3.8 2/include/CppUTest ディレクトリ
* cpputest-3.8 2/include/CppUTestExt ディレクトリ
* cpputest-3.8 2/src/CppUTest ディレクトリ
* cpputest-3.8 2/src/CppUTestExt ディレクトリ
* cpputest-3.8 2/src/Platforms/Gcc/UtestPlatform.cpp

STM32CubeIDEプロジェクトのファイル構成は下図になります。

![cpputest_prj_tree.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/9811df69-0ddc-77ee-7e24-a170db2743bd.jpeg)

## リソース
NT-Shell・CppUTestを使用した簡易テスト環境のシステムのリソースについて説明します。

### タスク構成
タスク、タスクが使うリソースは下表のとおりです。

| ファイルパス | タスク名 | タスクの概要 | 使っているOSリソース |
| - | - | - | - |
| Application/led_blink.c | led_blink_task | LED3（LD3）の点滅を行う | なし |
| Application/ntshell_task.c | ntshell_task | ・シリアルコンソールからの文字列入力・出力をおこなう。シリアルコンソールの入力文字列がコマンド文字列に一致する場合はusercmd.cのコマンドを実行する。<br>・同期・通信機能のテスト（テストグループ名SyncAndComTestGroup）でコマンドをメールで送信し、レスポンスをsync_and_com_provider_taskからメールで受信する。受信したメールの内容でテストOK・NGを判定する。 | ・メールのコマンドリクエスト用の固定長メモリプール（mpfid_request）<br>・コマンドリクエスト用ののメールボックス（mbxid_request） |
| test/mtkernel_api_lerning_test/mtkernel_task_manegemnrt.c | mtkernel_task_manegemnrt_test_task | ・テストグループ名 MtKernelApiLerningTestGroupのテスト名 CreateAndStartTskで保存したタスクIDを確認するテストで使用する。<br>・LED2（LD2）を点滅する | なし |
| test/mtkernel_api_lerning_test/sync_and_com_provider.c | sync_and_com_provider_task | 同期・通信機能のテスト（テストグループ名SyncAndComTestGroup）でntshell_taskがメールで送信したコマンドを受信し、レスポンスをメールでntshell_taskに返信する。 | ・メールのレスポンス用の固定長メモリプール（mpfid_response）<br>・レスポンス用のメールボックス（mbxid_response） |


### OS以外で必要なリソース
#### シリアル通信
シリアルコンソールの入力文字列をNT-Shellが解釈します。入力文字列がコマンドであれば該当コマンドを実行します。
NT-Shellでシリアル通信を使うためシリアル通信のインタフェースがリソースとして必要です。

### 必須でないが使っているリソース
つぎのリソースは必須ではないですが、コマンドで使っています。

* GPIO LED 3個（write_lednコマンドで使用。n = 1〜3）
* GPIO タクトスイッチ（read_userbuttonコマンドで使用）


## カーネルの変更点について
NT-Shell・CppUTestを使用した簡易テスト環境はNT-Shellの入出力インターフェースとしてシリアル通信を利用します。
μT-Kernel 3.0 BSP2で実装されていたデバイスドライバは以下でした。

* A/Dコンバータ
* I2C

シリアル通信のデバイスドライバがなかったため、[μT-Kernel 3.0 BSP](https://github.com/tron-forum/mtk3_bsp)のシリアル通信デバイスドライバを移植しました。
そのような理由から今回のNT-Shell・CppUTestを使用した簡易テスト環境はBSP2だけではなく、BSPのコードが混じっています。

BSP2のデバイスドライバはSoCメーカー提供のコード（== 統合開発環境が生成するコード）を使う構造のようで、BSPのデバイスドライバの構造とは若干違うように見えました。

もし今後BSP2でシリアル通信のデバイスドライバが実装されたらNT-Shell・CppUTestを使用した簡易テスト環境もBSP2のシリアル通信デバイスドライバを使うようにしたいと思います。


# テスト
NT-Shell・CppUTestを使用した簡易テスト環境では記事冒頭に書いたように、

>テストは通常、組込みソフトウェアのアプリケーションロジックの確認を目的に作成、実行することがメインだと思います。
>ただ、ソフトウェア開発のプロセスにおいて開発者の不安はアプリケーションロジックの動作以外にも存在すると思います。

ソフトウェア開発で開発者が不安を感じるであろう項目のテストを書き、確認しました。
この章では今回書いたテストについて説明します。

## μT-Kernel 3.0の学習テスト
μT-Kernel 3.0の使い方を学ぶ目的のテストです。
μT-Kernel 3.0仕様書[^参考資料1]に書いてある機能を確認します。

### μT-Kernel 3.0の位置づけと構成
μT-Kernel 3.0は下図の位置付け・構成となっています。
つぎの文、図はμT-Kernel 3.0仕様書[^参考資料1]の引用です。

>μT-Kernel 3.0は、タスクのスケジューリングや同期・通信などリアルタイムOS本来の機能を提供する「μT-Kernel/OS(Operating System)」、
>システム管理向けの拡張機能を提供する「μT-Kernel/SM(System Manager)」、
>およびソフトウェアによるデバッガのための機能を提供する「μT-Kernel/DS(Debugger Support)」により構成される。

![μT-Kernel 3.0の位置づけと構成.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/34ad6939-d6b5-952c-5c8c-93676f071279.jpeg)

### テスト一覧
μT-Kernel 3.0の学習テストの一覧表です。
表に書いてある章番号はμT-Kernel 3.0仕様書[^参考資料1]の章番号です。

| μT-Kernel機能 | 機能分類 | API名称  | テストの目的 | テストグループ名 | テスト名 |
| - | - | - | - | - | - |
| μT-Kernel/OS | 4.1 タスク管理機能 | 4.1.12 tk_ref_tsk - タスク状態参照 | タスクの状態を参照するtk_ref_tskの使い方を学習する | MtKernelTaskManagementGroup | BasicUsage_tk_ref_tsk |
| μT-Kernel/OS | 4.4 同期・通信機能 | 4.4.3.3 tk_snd_mbx - メールボックスへ送信<br>4.4.3.4 tk_rcv_mbx - メールボックスから受信 | メールボックスの送信・受信のの使い方を学習する | SyncAndComTestGroup | BasicUsageMailbox |
| μT-Kernel/OS | 4.7 時間管理機能 | 4.7.1.7 tk_get_tim - システム時刻参照（TRON表現） | システム時刻を参照するtk_get_timの使い方を学習する | TimeManagementTestGroup | BasicUsage_tk_get_tim |
| μT-Kernel/OS | 4.7 時間管理機能 | 4.7.1.9 tk_get_otm - システム稼働時間参照 | システム稼働時間を参照するtk_get_otmの使い方を学習する | TimeManagementTestGroup | BasicUsage_tk_get_otm |
| μT-Kernel/SM | 5.4 I/Oポートアクセス | 5.4.1.7 in_w - I/Oポート読込み（ワード） | 評価ボードのUSER B1ボタンの状態をin_wで読み込めるか確認する | IOPortAccessSupportTestGroup | in_w_B1USER_Button_NotPressed |
| μT-Kernel/SM | 5.4 I/Oポートアクセス | 5.4.1.3 out_w - I/Oポート書込み（ワード） | 評価ボードのLED LD1に書込み、データレジスタを確認する | IOPortAccessSupportTestGroup | out_w_and_in_w |


## テストフレームワークCppUTestの学習テスト
NT-Shell・CppUTestを使用した簡易テスト環境はテストフレームワークCppUTestを使い、テストを書いています。
CppUTestを使ったことがない人はテストを書くことができないと思うので、CppUTestの使い方を学べるテストを書きました。

| テストグループ名 | テスト名 | テスト目的  |
| - | - | - |
| CpputestUsageGroup | setup | テストグループとsetupの動きを確認する。<br>setupCpputestUsageGroupのsetupでテストグループ内で共有する変数が初期化されることを確認する。 |
| CpputestUsageGroup_2 | setup | テストグループ名が異なればテスト名は重複可能なことを確認する。<br>テストグループCpputestUsageGroup_2のsetupが呼び出されていることを確認する。 |
| CpputestUsageGroup | setupGlobalVariableReference | CpputestUsageGroupのsetupでグローバル変数の初期化を確認する |
| CpputestUsageGroup_2 | setupGlobalVariableReference | CpputestUsageGroup_2のsetupでグローバル変数の初期化を確認する |
| CpputestUsageGroup | BasicUsage_BITS_EQUAL_Assertions | ビットパターンをチェックするアサーションBITS_EQUALを学習するテスト |

ビットパターンをチェックするアサーションBITS_EQUALはマイコンのレジスタをチェックするなど組込みソフトウェアのテストで重宝しそうと思いました。

## C++の学習テスト
プログラミング言語に不安があるという場合もあるかと思います。テストを使ってプログラミング言語を学習するというアプローチもよいと思います。今回はC++の学習にテストを使いました。

| テストグループ名 | テスト名 | テスト目的  |
| - | - | - |
| CppLerningTestGroup | defaultArgumentNone | デフォルト引数を使わないの場合の確認 |
| CppLerningTestGroup | defaultArgument_arg2 | 第2引数がデフォルト引数の場合の確認 |
| CppLerningTestGroup | defaultArgument_arg1_2 | 第1、2引数がデフォルト引数の場合 |
| CppLerningTestGroup | BasicUsageBinaryLiterals | 2進数リテラル（C++14）の学習テスト |
| CppLerningTestGroup | BasicUsageDigitSeparators | 数値リテラルの桁区切り文字(C++14)の学習テスト |

2進数リテラル（C++14）、数値リテラルの桁区切り文字(C++14)はこちらを参考にさせていただきました。
今回はじめて使いましたがマイコンのレジスタ書込み、読込みなど低レイヤのコードで使うと有用と思いました。

>C++17 が組み込み開発にもたらす恩恵（その２）

https://qiita.com/hira_kuni_45/items/a1c979d1ec385c06cc52

>cpprefjp - C++日本語リファレンス

https://cpprefjp.github.io/index.html

## アプリケーションロジックのテストをChatGPTで生成した事例
こちらの記事のアプリケーションの計算ロジックのテストをChatGPTで生成することを試してみました。
ChatGPTのモデルは**ChatGPT 4o**にしました。

>ChatGPTを使い簡単に単体テストコードを作る方法(CppUTest環境)
https://www.monokuma12.com/entry/cpputest_chatgpt#google_vignette

計算ロジックは条件付きの引き算です。引数、計算結果が-100以下にならなければtrueを返します。
ファイルはこちらのパスで関数名はMinusです。

* mtk3_bsp2_easy_test_env/test/calclogic_ChatGPT_support/calclogic.c

### ブラックボックステストの生成
前述の記事のとおり最初は以下のプロンプトでブラックボックステストの生成を依頼しました。

```
つぎのソースコードに対して、境界値テスト、同値テストを含んだブラックボックステストをくってください。テストフレームワークはCppUTestとします。
テストのファイル名はcalclogic_ChatGPT_support.cppとしてください。

bool Minus(
    int data1,
    int data2,
    int *ans)
{
    int min = -100;
    *ans = data1 - data2;
    if ((min >= data1) || (min >= data2) || (min >= *ans))
    {
        return false;
    }
    return true;
}
```

ChatGPTはつぎの9つのテストコードを生成しました。

| テストグループ名 | テスト名 | テスト観点  |
| - | - | - |
| MinusTestGroup | Minus_ValidInput_WithinBounds | 引数1と引数2が範囲内で、期待通りの結果が得られるかどうかをテスト |
| MinusTestGroup | Minus_BoundaryCondition_ExactMin | 引数1、引数2または計算結果がちょうど-100の場合の挙動を確認 |
| MinusTestGroup | Minus_BoundaryCondition_BelowMin | 引数1、引数2または計算結果が-100未満の場合の挙動を確認 |
| MinusTestGroup | Minus_BoundaryCondition_AboveMin | 引数1または引数2が-100より大きい範囲内のケースをテスト |
| MinusTestGroup | Minus_SameValues | 同じ値を引いたときの結果が0になることを確認 |
| MinusTestGroup | Minus_LargePositiveDifference | 正の数同士の引き算が期待通りに動作するか確認 |
| MinusTestGroup | Minus_LargeNegativeDifference | 引数2が引数1より大きい場合の計算が正しく行われるか確認 |
| MinusTestGroup | Minus_MaxValueInput | 引数1がINT_MAXの場合の挙動をテスト |
| MinusTestGroup | Minus_MinValueInput | 引数1がINT_MINの場合にfalseを返すことを確認 |

生成されたテストコードを目視確認した限り、要求した境界値テスト、同値テストのテスト観点をおさえた有効なテストコードだと思いました。
私はINT_MAX、INT_MINのテストケースは想像していなかったので感心しました。また、わかりやすいテスト名を命名してくれることに驚きました。

生成されたテストコードに以下の追加を行いました。
* ChatGPTに日本語コメントを追加してもらう
* limits.hのインクルード（INT_MAX、INT_MINを参照するため）
* テスト対象のロジックMinus関数のヘッダファイルをインクルード

テストのファイル以下です。
* mtk3_bsp2_easy_test_env/test/calclogic_ChatGPT_support/calclogic_ChatGPT_support.cpp

テストコードはビルド、テストOKであることを確認しました。

### ホワイトボックステストの生成
ChatGPTにカバレッジを満たすホワイトボックステストの生成を依頼しました。
プロンプトはこちらです。

```
先ほどのソースコード（Minus関数）に対して、C2カバレッジを満たすホワイトボックステストをくってください。
テストフレームワークはCppUTestとします。
```

ChatGPTはつぎの9つのテストコードを生成しました。

| テストグループ名 | テスト名 | テスト観点  |
| - | - | - |
| MinusC2CoverageTestGroup | Minus_MinLessThanAns | min >= *ans の条件が偽の場合 |
| MinusC2CoverageTestGroup | Minus_MinGreaterThanAns | min >= *ans の条件が真の場合 |
| MinusC2CoverageTestGroup | Minus_MinLessThanData2 | min >= data2 の条件が偽の場合 |
| MinusC2CoverageTestGroup | Minus_MinGreaterThanData2 | min >= data2 の条件が真の場合 |
| MinusC2CoverageTestGroup | Minus_MinLessThanData1 | min >= data1 の条件が偽の場合 |
| MinusC2CoverageTestGroup | Minus_MinGreaterThanData1 | min >= data1 の条件が真の場合 |

生成してくれたテストコードを目視確認した限り、カバレッジを満たすテストコードを生成してくれました。


## マイコンアーキテクチャ（Cortex-M7）のテスト
以前開発していたマイコンからアーキテクチャが異なるマイコンを使い開発する、などの場合、アーキテクチャの違いが気になったりしませんか?
テストでアーキテクチャを学習し、不安を少なくできないかと考えました。

マイコンアーキテクチャにより挙動が異なることとして最初に頭に浮かんだのはint型のサイズでした。
Cortex-M7マイコンは32bitマイコンなのでint型のサイズも4バイトになるかと思ったのでその確認を行うテストを書き、確認しました。

| テストグループ名 | テスト名 | テスト観点  |
| - | - | - |
| CortexM7ArchitectureGroup | intSize | int型のデータサイズの確認 |


## ターゲットマイコンの仕様を確認するテスト
SoCメーカーの異なるマイコンシリーズなどではマイコンの仕様が異なっている、ということはないでしょうか?
そういった違いが開発を推進するうえでの不安要素になったりしていないでしょうか?

マイコン毎に異なる仕様で思いついたこととして、エンディアンがありました。
エンディアンを確認するテストを書き、確認しました。
今回のターゲットマイコンはリトルエンディアンのため、リトルエンディアンであることを確認しています。

| テストグループ名 | テスト名 | テスト観点  |
| - | - | - |
| CPUSpecificationsGroup | endian | リトルエンディアンであることの確認 |


## 将来の問題のテスト
いまは問題が起きていなくても将来起きる問題に不安を持つ方もいるのではないでしょうか。
将来起きる問題としてありがちなのは時間の経過で明らかになる不具合があると思います。
今回は2038年問題の対策状況のテストを書き、確認しました。

| テストグループ名 | テスト名 | テスト観点  |
| - | - | - |
| FutureProblemTestGroup | pre2038problem | 2038年問題未発生であることを確認する |
| FutureProblemTestGroup | 2038problem | 2038年問題が対策できているか |

テストの結果、2038年問題が発生しないことが確認できました。
今回の例ではtime_t型の定義をコードで確認すれば、実機確認しなくてもわかりましたね💦

確認したい問題がマイコンが使っているライブラリに依存している時は実機での確認も有効だと思います（今回の例ではtime_t型の定義になります）。

# まとめ
NT-Shell・CppUTestを使用した簡易テスト環境のまとめです。

# 必要なリソース
必要なリソースです。
別のマイコンに移植する際のヒントになればと思います。

## シリアル通信
シリアルコンソールとNT-Shellを経由してコマンドを解釈、実行するシステムのためシリアル通信のインターフェースが必要です。

## ROM・RAM使用量
下図は現在のROM・RAM使用量です。

![rom_ram_used.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171866/08473389-4520-6c22-9598-31e87b5c588a.jpeg)

ROM使用量は1024 KBのうちの178.35 KBを使用し、使用量は17.42%です。
RAM使用量はRAM_D1エリアの320 KBのうち18.11 KBを使用し、使用量は5.66%です。

# 性能
性能についてです。

## ダウンロード速度
フラッシュROMへのダウンロード時間は10秒くらいでした。

# 不具合
## 一部のテストの不具合
テストグループ名 MtKernelApiLerningTestGroupのテスト名 CreateAndStartTskのテストですが、テストを2回おこなうとハードフォールトが発生します。
テスト内容的には

* tk_cre_tsk
* tk_sta_tsk
* tk_ref_tsk

のAPIを呼び出しており、タスク生成→タスク開始→タスク状態参照の動作を確認する目的のテストです。
現在のところ原因がわからず解決できていないため無視テスト（IGNORE_TEST）として、テストしないようにしています。

## RAMにコード配置、実行
素早くテストするための方法としてRAMにコード配置、実行することが考えれれます。
フラッシュROMにコード配置、実行するのに比べ、ダウンロード時間の短縮が期待できます。
フラッシュROMの書き換え回数を心配する必要もなくなります。

上記の理由からRAMにコード配置、実行を試してみましたが実行時エラーとなりました。
いまのところ解決方法がわからないためRAMにコード配置・実行は未対応です。

# 拡張
NT-Shell・CppUTestを使用した簡易テスト環境の拡張についてです。

## ネットワーク対応
今回の評価ボードNucleo-H723ZGは有線イーサネットワークがあります。
ネットワークの対応を行うことで更にテストのしやすさの面でも使い勝手の向上が期待できそうです。
例えばGitHubへのプッシュでGitHub Actionsのワークフローが走り、実機テストを行う、などが思いつきます。
ネットワーク対応が実現できれば応用範囲も広がりそうです。

# ライセンス
NT-Shell・CppUTestを使用した簡易テスト環境で使っているライブラリのライセンスです。

## NT-Shell
MIT

https://cubeatsystems.com/ntshell/license.html

## CppUTest
BSD-3-Clause license

https://github.com/cpputest/cpputest?tab=BSD-3-Clause-1-ov-file#readme

## μT-Kernel 3.0およびBSP2
T-License 2.2

https://www.tron.org/download/index.php?route=information/information&information_id=79

## CMSIS
Apache License Version 2.0, January 2004

http://www.apache.org/licenses/

## STM32H7xx_HAL_Driver
BSD-3-Clause

https://opensource.org/licenses/BSD-3-Clause

## 上記以外
上記以外のコードはMITライセンスとします。

MIT

https://opensource.org/license/mit


[^参考資料1]: [参考資料1: μT-Kernel 3.0 仕様書 2019 年 12 月 初版](https://www.tron.org/wp-content/uploads/2015/03/utk3_30000_191211_ja.pdf)