marunomaruno-memo

marunomaruno-memo

Lego Mindstoms NXT - NXC プログラミング (4) 関数

2008年01月13日 | LEGO
■ 関数

関数は C 言語とほぼ同じです。ただ、inline キーワードを使って、関数をインライン展開できます。ただし、インライン関数をネストさせることはできません。
また、safecall キーワードを使って、この関数の実行を排他的に行うことができます(★)。

---
[safecall] [inline] 戻り値型 名前(引数リスト)
{
// 関数のコード
}
---

ただし、関数のプロトタイプ宣言はできないので、その関数を使う前に関数を定義しておかなければなりません。ちょっと残念な仕様です。
ただし、関数が使えることで、main タスクの見通しはかなりよくなりました。


■ Trybod01 の各機能を関数にする

Trybod01 では、タッチするまで前進する、暗くなるまで前進する、などがそれぞれコーディングされています。これらを、関数を使ってコーディングしてみます。

---
/**
 * Trybot02.nxc
 * 「トライボット」使用
 * 前進し、そこでタッチしたものをつかみ、後退、回転し、
 * 黒線があるところまで前進し、黒線のところでつかんだものを離す。
 * @author maruno
 * @version 1.0, 2008-01-08
 * @since 1.0
 */
#include "NXCDefs.h"

#define PUT_THRESHOLD 51 // 物を置くしきい値 (cm)
#define SOUND_THRESHOLD 50 // 音のしきい値 (dB)
#define LIGHT_THRESHOLD 40 // 光のしきい値

/**
 * タッチされるまで前進する。
 */
inline void ForwardUntilTouch()
{
    OnFwd(OUT_BC, 75);
    until (SENSOR_1 == 1);
    Off(OUT_BC);
}

/**
 * 暗くなるまで前進する。
 * @param threshold 暗くなるしきい値
 */
inline void ForwardUntilDark(long threshold)
{
    OnFwd(OUT_BC, 75);
    until (SENSOR_3 <threshold); threshold);
}

/**
 * main タスク。
 */
task main()
{
    // センサーの設定
    SetSensorTouch(S1);     // タッチセンサーの設定
    SetSensorSound(S2);     // 音センサーの設定
    SetSensorLight(S3);     // 光センサーの設定
    SetSensorLowspeed(S4);  // 超音波センサーの設定

    // 物が置かれるまで停止
    StopUntilPut(PUT_THRESHOLD);

    // タッチするまで前進
    ForwardUntilTouch();

    // 音がするまで停止
    StopUntilAnySound(SOUND_THRESHOLD);

    // アームを閉じる
    CloseArm(500);

    // 後退
    ReverseMilliSeconds(500);

    // 回転
    RotetoLeftMilliSeconds(500);

    // 黒線のところまで前進して停止
    ForwardUntilDark(LIGHT_THRESHOLD);

    // アームを開く
    OpenArm(500);
}
---



■ 関数部分を別ソースに書く

Trybot02 の関数部分を別ソースとして書いてみます。
そうすることで、自分で作った関数を別プログラムでも使いまわすことができます。

関数は、Trybot02 で作ったモーター関係の関数と、センサー関係の関数として、別ソース "MotorFuncs01.nxc"、"SensorFuncs01.nxc" として作ります。これらを #include を使ってインクルードします。

ただ、これらの関数のソースの編集で、BricxCC を使う場合、エディタでキーワードを強調されるので、拡張子は "nxc" にしておいた方が便利でしょう。

---
/**
 * Trybot03.nxc
 * 「トライボット」使用
 * 前進し、そこでタッチしたものをつかみ、後退、回転し、
 * 黒線があるところまで前進し、黒線のところでつかんだものを離す。
 * @author maruno
 * @version 1.0, 2008-01-11
 * @since 1.0
 */
#include "NXCDefs.h"
#include "MotorFuncs01.nxc"   // モーター関係 (1)
#include "SensorFuncs01.nxc"  // センサー関係 (2)

#define PUT_THRESHOLD 51 // 物を置くしきい値 (cm)
#define SOUND_THRESHOLD 50 // 音のしきい値 (dB)
#define LIGHT_THRESHOLD 40 // 光のしきい値

/**
 * main タスク。
 */
task main()
{
    // センサーの設定
    SetSensors(MASK_ALL_SENSORS); // (3)

    // 物が置かれるまで停止
    StopUntilPut(PUT_THRESHOLD);

    // タッチするまで前進
    ForwardUntilTouch();

    // 音がするまで停止
    StopUntilAnySound(SOUND_THRESHOLD);

    // アームを閉じる
    CloseArm(500);

    // 後退
    ReverseMilliSeconds(500);
    
    // 回転
    RotetoLeftMilliSeconds(500);
    
    // 黒線のところまで前進して停止
    ForwardUntilDark(LIGHT_THRESHOLD);

    // アームを開く
    OpenArm(500);
}
---


□ #include "MotorFunctions01.nxc" // モーター関係 (1)
□ #include "SensorFunctions01.nxc" // センサー関係 (2)

それぞれの関数のソースをインクルードする。
こうすることで、ソースを分割して記述できるのでモジュール化することがしやすくなる。
ただし、MotorFunctions01.nxc などのソースのコンパイルエラーが、main タスクでコンパイルエラーとして出る場合もあるようだ。(★)


□ SetSensors(MASK_ALL_SENSORS); // (3)

4 つのセンサーを使う場合のマクロ定数。定義は、 "SensorFuncs01.nxc" で行っている。


---
#ifndef MOTOR_FUNCS01_H_  // (4)
#define MOTOR_FUNCS01_H_  // (4)
/**
 * MotorFuncs01.nxc
 * モーターを動かす関数
 * @author maruno
 * @version 1.0, 2008-01-11
 * @since 1.0
 */

/**
 * タッチされるまで前進する。
 */
inline void ForwardUntilTouch()
{
    OnFwd(OUT_BC, 75);
    until (SENSOR_1 == 1);
    Off(OUT_BC);
}

/**
 * 暗くなるまで前進する。
 * @param threshold 暗くなるしきい値
 */
inline void ForwardUntilDark(long threshold)
{
    OnFwd(OUT_BC, 75);
    until (SENSOR_3 <threshold); threshold);
}

#endif // (4)
---


□ #ifndef MOTOR_FUNCS01_H_ // (4)
□ #define MOTOR_FUNCS01_H_ // (4)
□ #endif // (4)

ヘッダーの2重インクルードの防止。


---
#ifndef SENSOR_FUNCS01_H_
#define SENSOR_FUNCS01_H_
/**
 * SensorFunctions01.nxc
 * センサー関連の関数
 * @author maruno
 * @version 1.0, 2008-01-11
 * @since 1.0
 */

#define MASK_S1 (0x01 << S1) // (5)
#define MASK_S2 (0x01 << S2) // (5)
#define MASK_S3 (0x01 << S3) // (5)
#define MASK_S4 (0x01 << S4) // (5)
#define MASK_ALL_SENSORS (MASK_S1+MASK_S2+MASK_S3+MASK_S4) // 4つのセンサーすべて // (6) /** * センサーを設定する。 * @param mask 使うセンサーのマスク * 76543210 * -------1 タッチセンサー * ------1- 音センサー * -----1-- 光センサー * ----1--- 超音波センサー */ inline void SetSensors(int mask) { if (mask & MASK_S1) SetSensorTouch(S1); // タッチセンサーの設定 if (mask & MASK_S2) SetSensorSound(S2); // 音センサーの設定 if (mask & MASK_S3) SetSensorLight(S3); // 光センサーの設定 if (mask & MASK_S4) SetSensorLowspeed(S4); // 超音波センサーの設定 } #endif ---


□ #define MASK_S1 (0x01 << S1) // (5)
□ #define MASK_S2 (0x01 << S2) // (5)
□ #define MASK_S3 (0x01 << S3) // (5)
□ #define MASK_S4 (0x01 << S4) // (5)

それぞれのセンサーに対応したビット位置を ON にするようなマスクを作る。


□ #define MASK_ALL_SENSORS (MASK_S1+MASK_S2+MASK_S3+MASK_S4) // 4つのセンサーすべて // (6)

4 つのセンサーに対応するビットをすべて ON にしたマスクをつくる。


■ センサーの値を表示する関数

つぎのように、センサーの値を表示する関数を作り、使います。

---
/**
 * Sensor02.nxc
 * 「トライボット」使用
 * センサーの値を読み取り、表示する。
 * @author maruno
 * @version 1.0, 2008-01-08
 * @since 1.0
 */
#include "NXCDefs.h"

/**
 * センサー名を格納した文字列定数の配列
 */
const string SENSOR_NAME[] = {
        "Touch",
        "Sound",
        "Light",
        "US",
};  // (1)

/**
 * センサーポートを指定し、それに対するセンサー名を取得する。
 * @param sensor センサーのポート番号
 */
string getSensorName(int sensor)
{
    string name;
    switch (sensor) {
    case S1:
        name = "Touch";
        break;
    case S2:
        name = "Sound";
        break;
    case S3:
        name = "Light";
        break;
    case S4:
        name = "US";
        break;
    default:
        name = "undef";
        break;
    }
    return name;
}

/**
 * ディスプレイの指定された行に指定されたセンサーの値を表示する。
 * @param line 表示する行
 * @param sensor センサーのポート番号
 */
void OutSensorValue(int line, int sensor)  // (2)
{

    int num = ((sensor != S4) ? Sensor(sensor) : SensorUS(S4));  // (3)
    string value = NumToStr(num);
    string name = getSensorName(sensor); // SENSOR_NAME[sensor];  // (4)
    string msg = StrCat(name, " = ", value);
    TextOut(0, line, msg);
}

/**
 * main タスク。
 */
task main()
{
    // センサーの設定
    SetSensorTouch(S1);     // タッチセンサーの設定
    SetSensorSound(S2);     // 音センサーの設定
    SetSensorLight(S3);     // 光センサーの設定
    SetSensorLowspeed(S4);  // 超音波センサーの設定

    while (true) {
        // タッチセンサーの値の表示
        OutSensorValue(LCD_LINE1, S1);

        // 音センサーの値の表示
        OutSensorValue(LCD_LINE2, S2);

        // 光センサーの値の表示
        OutSensorValue(LCD_LINE3, S3);

        // 超音波センサーの値の表示
        OutSensorValue(LCD_LINE4, S4);

    }
}
---



□ const string SENSOR_NAME[] = {"Touch", ..."US", }; // (1)

文字列定数の配列を定義します。


□ void OutSensorValue(int line, int sensor) // (2)

関数の定義。この関数は OutSensorValue 関数から呼ばれるので、inline キーワードはつけない。NBC では、インライン関数のネストはできないことに注意。


□ int num = ((sensor != S4) ? Sensor(sensor) : SensorUS(S4)); // (3)

超音波センサーと他のセンサーとは取得方法に違いがあることに注意。


□ string name = getSensorName(sensor); // SENSOR_NAME[sensor]; // (4)

配列を使って取得すると、なぜか実行時エラーになる。メッセージは「file error!」。なぜ?★



■ サブルーチン

C 言語には関数しかありませんが、NXC には、「サブルーチン」という機能があります。NXC では、inline でない関数で、戻り値がないものです。もっとも、これは NXC ではなく、これの前提になっている NQC の文法を引き継いでのことと考えられますので、とくに使わなくてもいいでしょう。(★)


以上


最新の画像もっと見る

コメントを投稿