C/C++言語で簡単なプログラムを動かそう (連載2)
今回は、C/C++言語でLチカやファイル読み書きをやってみます。
Rust言語ではありません。使い慣れたC/C++言語で、できることを確認しておきたい回です。
ハードウェアを操作することができることをLチカで確認したいと思います。それと、もう片方で動作しているLinuxのファイルシステムにあるファイルの読み書きも確認したいと思います。
1.Lチカ事前準備
Lチカなので、LEDを選定しなければいけません。
今回はRaspberry Pi 4Bボードに搭載されているLEDを使います。
LEDが二つ付いています。緑と赤です。
このうち緑のほうは、SDカードのアクセスインジケーターとして使用されています。これをGPIO42から制御できるように設定変更します。
[設定方法]
SOLIDは使用しません。普通にRaspberry Pi 4BのLinuxコンソールから操作します。
設定内容:Linuxでの点滅イベントを無し(none)に設定
$ sudo sh -c 'echo none > /sys/class/leds/led0/trigger'
その後、sudo rebootを入力してリブートします。
これでRaspberri Pi 4B側の準備は終了です。
2.C言語で、LED点滅プログラムを書く
前回同様、こちらのWEBサイトを参考にし、新規プロジェクトを作成します。
main.cppファイルのslo_main関数を充実させていき、GPIO42を一定周期でHigh⇒Low⇒High⇒Low… することを繰り返すプログラムを作成します。
ここでは、C++ではなく、C言語で書きます。理由は筆者がC言語の方が扱いなれているからです。実は、組み込み用途ではC++をほぼ使ったことがない、と言っても過言ではないのです。C++はWindowsアプリ用途でよく使いますが。
新規プロジェクトを作成すると、main.cppが作成されます。拡張子が”*.cpp”ですが、気にせずにCでプログラムを書いてみます。
[プログラム例]
#include <solid_log.h>
#include <kernel.h>
#define GPIO_BASE (0xFE200000)
#define GPIO_NUM (42)
void green_led_init()
{
uint32_t *reg = ((uint32_t *)(GPIO_BASE + ((GPIO_NUM / 10) << 2))); // GPFSEL4
int mode = 1; // output
*reg = (*reg & ~(7 << ((GPIO_NUM % 10) * 3))) | (mode << ((GPIO_NUM % 10) * 3));
}
void green_led_update(bool new_state)
{
uint32_t *reg = (uint32_t *)(GPIO_BASE + ((GPIO_NUM / 32) < < 2) + (new_state ? 0x1c /* GPSET1 */ : 0x28 /* GPCLR1 */));
*reg = 1 << (GPIO_NUM % 32);
}
extern "C" void slo_main()
{
SOLID_LOG_printf("Starting LED blinker\n");
green_led_init();
while (true)
{
// Turn on the LED
green_led_update(true);
dly_tsk(200'000);
// Turn off the LED
green_led_update(false);
dly_tsk(200'000);
}
}
作成したプログラムをビルドし、Raspberry Pi 4Bボードにダウンロードし、実行すると、LEDが点滅します。
以上、ものすごく見慣れた、ごく普通のCプログラムでLチカが書けました。
せっかくなので、C++でもLチカを書いてみたいと思います。
3.C++言語で、LED点滅プログラムを書く
再度、新規プロジェクトを作成し、先ほどのCプログラムを、C++で書いてみます。
[プログラム例]
#include <cstdint>
#include <cstdlib>
#include <solid_log.h>
#include <kernel.h>
namespace {
namespace green_led {
constexpr std::uintptr_t GPIO_BASE = 0xFE200000UL;
constexpr std::size_t GPIO_NUM = 42;
void init()
{
auto reg = reinterpret_cast<volatile std::uint32_t *>(GPIO_BASE + ((GPIO_NUM / 10) << 2)); // GPFSEL4
int mode = 1; // output
*reg = (*reg & ~(7 << ((GPIO_NUM % 10) * 3))) | (mode << ((GPIO_NUM % 10) * 3));
}
void update(bool new_state)
{
auto reg = reinterpret_cast<volatile std::uint32_t *>(GPIO_BASE + ((GPIO_NUM / 32) << 2)
+ (new_state ? 0x1c /* GPSET1 */ : 0x28 /* GPCLR1 */));
*reg = 1 << (GPIO_NUM % 32);
}
} // namespace green_led
} // namespace
extern "C" void slo_main()
{
SOLID_LOG_printf("Starting LED blinker\n");
// Configure the LED port
green_led::init();
while (true) {
// Turn on the LED
green_led::update(true);
dly_tsk(200'000);
// Turn off the LED
green_led::update(false);
dly_tsk(200'000);
}
}
先程作成した、Cのプログラムと、似ているようで違いますね。
例えば、Cの場合、定数は#defineで定義することができ、よく見るコードです。
しかし、プログラムが大きくなると、いったいどこで#define定義していたのかわかりにくかったり、あまり関係なさそうなソース・ヘッダーファイル内で定義してしまっていて、もう動かせなかったりします。。。
C++では、namespaceの中に入れて、その定数の生存範囲を明示的に表せるため、本当に必要な場所に定数の定義ができます。
このように、C++では「あやふや」が少し解消されている、という感じがします。この「あやふや」の解消が、プログラムが大きくなった際に、保守性や堅固性として見えてくるのでしょう。
作成したプログラムをビルドし、Raspberry Pi 4Bボードにダウンロードし、実行します。先ほどと同じく、LEDが点滅します。
4.デバッグもできる
ちなみに、C++でもデバッガ機能も使えるので、ブレークを設定したり、ステップ実行したりすることもできます。
ブレークポイントを貼ってみた:
ステップオーバーで1行実行してみた:
5.組み込みファームウェアでCとC++?
せっかくCとC++で(簡単ではありますが)同じプログラムを書いてみたので、もうちょっと考えてみます。
なぜ今までC++ではなく、Cを好んで使ってきたのか。
Cって、コンパイルした後に生成されるオブジェクト、とても分かりやすいと思いませんか?
アセンブラリストで見てみると、Cのソース行との対比がよくわかる。ソース行が意図したアセンブラに変換されているイメージです。全部ではないですが。また、極力ヒープ領域を使わずに(mallocを使わずに)プログラムを書けるので、RAMの使用量を調整しやすいです。
すなわち、ある程度自分の思い通りのオブジェクトが作れる、したがって、デバッグがしやすい、というイメージがあります。
一方、C++は、明示的にソースコードに表れないようなところがある気がします。
Cだと、関数が関数としてあらかじめ実体がいらっしゃる(クラスをnewしなくてよい)、アドレスが最初から決まっているので、もうあとは関数コールすれば良いという、手軽さ。
諸々あり、
C++だとプログラムに必要なメモリサイズが大きくなるのではないか。
明示的にソースコードに表れないようなところでもしCPUがおかしな動作をしたら、デバッグがとってもとってもやりにくそう!
Cは自分が意図したコードだけがコンパイラで生成されるため、何かあったときにごりごりデバッグしやすいしメモリサイズの調整がしやすい。
という、まぁ勝手な価値観により、C言語を使い続けるに至っています。
ただしC++はCにないような安心感があります。
例えば。。。
Try and Catchでエラー処理をきっちり処理できる。(謎のリセット発生が減る)
オブジェクト化としてクラスを隠蔽しておくと、バージョンアップの際の影響範囲がわかりやすくメンテしやすい
関数オーバーロードや継承によって、似たような関数の増殖化を防げる。もしくは似たような関数を無理矢理一つの関数に押しこんで、なんだか使いにくくなることを防げる。
CもC++もどちらも特徴あり、結局のところシステムの規模感、実現したいアプリ/ファームウェアによって、うまく使い分けていければいいのだと思います。
閑話休題。
次は、SOLID-OSの特徴の一つ、Linux側との連携についてご紹介します。
6.Linuxファイルシステムのファイルを読み書きするプログラムを書く
前回作成した、Lチカプロジェクトを開きます。SOLIDのファイルシステムの使い方は、以下に記載されています。
http://solid.kmckk.com/doc/skit/current/os/filesystem.html
この使い方で、Linux側のファイルシステムをアクセスすることもできます。
SOLID for Raspberry Pi 4では、LinuxとのOS間通信を行う事が可能です。
この機能を用いて、\OSCOM_FS がLinuxファイルシステムにマップされます。例えば\OSCOM_FS\home\<ユーザ名>\<ファイル名>を指定するとLinux側のホームディレクトリにあるファイルをアクセスできる、という事です。
詳細は以下に記載あります。
solid-rapi4/system-rtos.md at main · KyotoMicrocomputer/solid-rapi4 (github.com)
では、Linux側のhomeフォルダにtextfile.txtを作成し、その中に”hello”と書いてみます。
[プログラム例]
・solid_fs.h をインクルード
#include <cstdint>
#include <cstdlib>
#include <solid_log.h>
#include <kernel.h>
#include "solid_fs.h"
・slo_main の先頭でファイルオープンし hello と書き込み、ファイルクローズ
extern "C" void slo_main()
{
int fd;
int result = SOLID_FS_Open(&fd, "\\OSCOM_FS\\home\\pi\\textfile.txt", O_CREAT | O_RDWR | O_TRUNC);
if (result == SOLID_ERR_OK)
{
size_t bytes_written;
result = SOLID_FS_Write(fd, "hello", 5, &bytes_written);
SOLID_FS_Close(fd);
}
SOLID_FS_Close(fd);
SOLID_LOG_printf("Starting LED blinker\n"); // Configure the LED port green_led::init();
:
作成したプログラムをビルドし、Raspberry Pi 4Bボードにダウンロードし、実行します。Linux側のターミナルから、textfile.txtが生成されていることを確認し、中身を見てみます。
hello 書けてました!
無事、SOLID OSからLinuxへのOS間通信が行え、ファイルシステムにアクセスすることができました!
今回は以上です。
次回は、SOLID-OSについてもう少し詳しくご紹介しようと思います。