LED制御プロジェクトの作成
では、この仮想ファイルシステムを使う方法で、LEDを点灯させるプロジェクトを作成してみましょう。
新しいプロジェクトの作成
まずはVisual Studioで、新しいプロジェクトを作成します。プロジェクトのテンプレートは、コンソールアプリ(.NET Core)を選択します。
プロジェクトの構成は、プロジェクト名をVirtualFile、ソリューション名をPieCore、としました。またプロジェクトの場所は、前回解説したsambaを利用して、Raspberry Piの共有フォルダを直接指定しています。つまりプロジェクトファイルは、Raspberry Pi上に作成されることになります。なお、PCのローカルフォルダに作成した場合は、アプリケーションの実行時に、プロジェクトフォルダをRaspberry Piにコピーします。
共有フォルダを指定してプロジェクトを作成すると、セキュリティ警告が表示される場合があります。今回は、自分で作成するプロジェクトであり問題ないので、そのままOKをクリックします。
プロジェクトの設定
プロジェクトが作成されたら、次に、CPU(ソリューション プラットフォーム)の設定を行います。メニューから、構成マネージャーを選択します。
ダイアログが表示されるので、アクティブ ソリューション プラットフォーム欄のところから新規作成を選びます。
新しいソリューション プラットフォームの設定画面が表示されたら、最初の入力欄のところを、linux-armに変更して、OKをクリックします。
構成マネージャーを閉じると、CPU(ソリューション プラットフォーム)が変更されます。これで、Raspberry Pi用のアプリケーションが作成できます。
クラスの作成
プロジェクトの準備ができましたので、Program.csにコードを追加していきましょう。先ほどの仮想ファイルシステムでの手順を、そのままクラスにしてみます。
// 仮想ファイルによるGPIO操作クラス class Gpio { // 仮想ファイルの起点パス private const string BasePath = "/sys/class/gpio/"; // 使用するGPIOピンの初期設定(3) public void Init(int pin) { // pinの初期化 if (!Directory.Exists($"{BasePath}gpio{pin}")) { using var sw = new StreamWriter($"{BasePath}export"); sw.Write(pin); // (1) } // ディレクトリの生成確認(2) while (!Directory.Exists($"{BasePath}gpio{pin}")) { Thread.Sleep(10); } } ~略~ }
Initメソッドでは、使用するGPIOピンの初期設定を行います(3)。/sys/class/gpio/gpio番号のディレクトリが存在しないとき、StreamWriterクラスを使って、仮想ファイルの/sys/class/gpio/exportにGPIOの番号を書き込んでいます(1)。番号を書き込んですぐには、/sys/class/gpio/gpio番号のディレクトリが作成されないので、作成されるまで待ちます(2)。
なお、ここでは、C#8.0の機能である、using変数宣言を使っています。 usingステートメントでは、スコープを示す{}が必要でしたが、using変数では、usingの有効範囲は、using変数のスコープと同じになります。
GPIOピンの設定
次に、GPIOピンの出力・入力の設定と、値を設定するメソッドを追加します。
// GPIOの値(4) public enum Value { OFF = 0, ON = 1 } // outの設定(5) public void SetOut(int pin) { using var sw = new StreamWriter($"{BasePath}gpio{pin}/direction"); sw.Write("out"); } // inの設定(6) public void SetIn(int pin) { using var sw = new StreamWriter($"{BasePath}gpio{pin}/direction"); sw.Write("in"); } // 値の設定(7) public void SetValue(int pin, Value val) { using var sw = new StreamWriter($"{BasePath}gpio{pin}/value"); sw.Write((int)val); //(8) }
SetOut/SetInメソッドでは、仮想ファイルの/sys/class/gpio/gpio番号/directionに、out、inの文字列を書き込みます(5)(6)。SetValuメソッドでは、/sys/class/gpio/gpio番号/valueに、引数で指定した値を書き込みます(7)。引数は、enum型として定義しています(4)。なお、仮想ファイルには、0または1の数値の文字列を書き込む必要があります。enum型でそのまま書き込むと、定数の文字列表現(例えばOFFなど)が書き込まれるので、ここでは(int)で数値型にキャストして、数値に変換しています(8)。
GPIOピンの参照
次に、GPIOピンの参照です。
// 値の参照 public Value GetValue(int pin) { string v = File.ReadAllText($"{BasePath}gpio{pin}/value"); //(9) return (Convert.ToInt32(v) == (int)Value.ON) ? Value.ON : Value.OFF; //(10) }
仮想ファイル/sys/class/gpio/gpio番号/valueの読み出しには、File.ReadAllTextメソッドを利用しています(9)。このメソッドは、ファイルのクローズ処理もメソッド内で完結しているので、シンプルなコードで記述できます。File.ReadAllTextメソッドで読み出した文字列は、いったん数値に変換してenum型の値と比較し、適合したenum型を返しています(10)。
GPIOピンの終了処理
最後に、終了処理のTerminateメソッドを追加します。
// 使用したGPIOピンの終了処理 public void Terminate(int pin) { if (Directory.Exists($"{BasePath}gpio{pin}")) { using var sw = new StreamWriter($"{BasePath}unexport"); sw.Write(pin); } }
GPIOピンの初期設定と同様に、仮想ファイルの/sys/class/gpio/unexportに番号を書き込んでいます。
スイッチとLED点灯のコード
クラスができたので、タクトスイッチが押されたら、LEDが点灯するコードを作成してみます。30秒間スイッチの判定を行い、30秒すぎたら終了します。
var gpio = new Gpio(); var p1 = 4; var p2 = 15; // GPIOの初期化 gpio.Init(p1); gpio.Init(p2); // GPIOのIN,OUT設定 gpio.SetOut(p1); gpio.SetIn(p2); // 30秒間処理を行う for (int i=0; i<300; i++) { // GPIO15がOFFになれば(スイッチが押されれば) (10) if (gpio.GetValue(p2) == Gpio.Value.OFF) { // LED点灯 gpio.SetValue(p1, Gpio.Value.ON); } else { // LED消灯 gpio.SetValue(p1, Gpio.Value.OFF); } Thread.Sleep(100); } // GPIOの終了処理 gpio.Terminate(p2); gpio.Terminate(p1);
ここでは、GPIO4番ピンをLED点灯用に出力としています。また、15番ピンをスイッチの判定のため入力に設定しています。スイッチの判定は、ぱっと見ると反対のように感じますが、GPIO15番ピンは、デフォルトでプルアップとして接続されています。そのため、GPIO15番の値は、初期状態でONになります。スイッチを押したときに、GNDに接続されるような回路とすると、スイッチを押せば、GPIO15番の値はOFFになります。
【注】プルアップ、プルダウン
GPIOなどの電子回路の入出力端子は、通常、電源やGND(グランド)に接続するようにして、どこにも接続していない状態は、できるだけ避けるようにします。端子を、電源に接続する回路はプルアップ、GNDに接続する回路は、プルダウンと呼びます。
Raspberry Piでは、内部的に、プルアップまたはプルダウンの接続になっています。また、プルアップ、プルダウンの接続は、ソフトウェアで切り替えることもできます。