NXT GT

 

NXT GTはBluetooth通信を利用したラジコン自動車です。OSEKはエンジン制御やABS(アンチロックブレーキ)などの自動車電子制御ソフトウェア用の規格ですので、nxtOSEKを使用したNXTのリアルタイム制御アプリケーションとしては適当な題材といえます。

    

     

NXT GTでは次のセンサ/サーボモータおよびハードウェアデバイスを使用しています:

  • ポートA サーボモータ(ステアリング制御用)
  • ポートB サーボモータ(左後輪駆動用)
  • ポートC サーボモータ(右後輪駆動用)
  • ポートS1 光センサ(右側), ポートS3 光センサ(左側)
  • ポートS2 超音波センサ
  • ポートS4 タッチセンサ
  • Abe UB 22S Bluetoothドングル
  • BUFFALO digital/analog game pad (PC用アナログゲームパッド)

このバージョンのNXT GTでは上記NXTセンサ全てを使用しているわけではありません。しかし、光センサや超音波センサを使用することで、例えばライントレース機能や障害物回避機能を持った自動車へと拡張することができます。一方、Bluetooth通信接続先であるPC側については、NXT GamePad という専用ユーティリティソフトウェアを使用することで、NXT GTのラジコン操作および内部データのロギングに対応しています。ロギングデータは下のようにCSVファイルとして保存することができ、MATLABやEXCELを用いたデータ解析が可能です。(下のグラフはMATLABで作成したものです)

 

カスタムプラスティックホイール:

NXT GTには当初RCXのモータサイクル用タイヤ/ホイールを装着していました。ラジコン自動車としてはそれで十分機能しますが、制御という意味ではもう一つ面白くありません。そこで本物のラジコン自動車で盛り上がっているドリフト仕様のNXT GTをつくってみました。ドリフト仕様のNXT GTではカスタムのプラスティックホイールを装着することで、タイヤのグリップ力が低下し、制御によってアンダーステア/オーバーステアという車両特性変化やスピンが可能になります。(慣性ドリフトなどの本格的なドリフト走行は、試した限りではパワー不足でできませんでした)

    

 

NXT GT組み立て方法:

NXT GTの組み立て方法について紹介するためのLDraw/ML CAD ファイルを作成しています:nxtGT.ldr
nxtGT.ldrを開くにはPhilo's Home Pageで紹介されているLEGO Mindstorms NXT LDraw Parts Library も必要になります。

 

NXT GT諸元:

諸元
重量 [g]
922 (バッテリ装着時)
慣性モーメント [kgm^2]
3.1x10^(-3)
ステアリング最大切れ角 [deg]
-40 to 40

最小回転半径 [cm]

47.5 (モータサイクルタイヤ装着時)
最高速度 [m/s]
1.3 (モータサイクルタイヤ装着時 @8V)
モータ最大トルク [N.cm]
16.7
モータ最大パワー [W]
3.10
静摩擦係数

前方向: 0.096 後方向: 0.110 横方向: 0.146 (カスタムプラスティックホイール)
前方向: 0.577 後方向: 0.677 横方向: 0.805 (モータサイクルタイヤ)

 

ソースコード:

NXT GTには次の主要制御機能が実装されています:

  • ステアリング比例制御
  • 電子制御デファレンシャル機能付き左右駆動輪独立制御(電子制御デファレンシャルはタッチセンサによりON/OFFできます)
  • NXT GamePadによるBluetooth通信ラジコン操作およびデータロギング機能

TaskControlタスクはNXT GT制御のコアタスクで10msec周期で起動されます。TaskControlタスクではステアリング制御、左右駆動輪制御、そしてBluetooth通信機能が実装されています。さらに超音波センサによる距離計測用タスク(TaskSonar)、液晶表示用タスク(TaskLCD)も実装されており、TaskSonarは50msec周期、TaskLCDは500msec周期で起動されています。初期化用タスクも含めると全部で4つのタスクがNXT GT上で実行される形になります。周期タスクは全てレートモノトニック スケジューリングで駆動されています。

samples\nxtgt\nxtgt.c

/* nxtgt.c */
#include "kernel.h"
#include "kernel_id.h"
#include "ecrobot_interface.h"

/* OSEK declarations */
DeclareCounter(SysTimerCnt);
DeclareTask(TaskInitialize);
DeclareTask(TaskControl);
DeclareTask(TaskSonar);
DeclareTask(TaskLCD);

/* Definitions */
#define MOTOR_STEERING     NXT_PORT_A
#define MOTOR_LEFT         NXT_PORT_B
#define MOTOR_RIGHT        NXT_PORT_C
#define STEERING_LIMIT             40 /* degree */
#define STEERING_P_GAIN             2 /* proportinal gain */
#define DIFF_GAIN_MAX            0.7F /* 1.0-DIFF_GAIN_MAX: max. differential effect */
#define NEUTRAL_DEAD_ZONE           2 /* degree */
#define PWM_OFFSET                 10 /* friction compensator */
#define EDC_ON                     -1 /* Electronic Differential Control: ON */
#define EDC_OFF                     1 /* Electronic Differential Control: OFF */

static S8 EDC_flag;                   /* EDC flag */

/* Prototypes */
S32 FrictionComp(S32 ratio, S32 offset);

/* OSEK hooks */
void StartupHook(void){}
void ShutdownHook(StatusType ercd){}
void PreTaskHook(void){}
void PostTaskHook(void){}
void ErrorHook(StatusType ercd){}

/* nxtOSEK hooks */
void ecrobot_device_initialize(void)
{
  ecrobot_set_light_sensor_active(NXT_PORT_S1);
  ecrobot_set_light_sensor_active(NXT_PORT_S3);
  ecrobot_init_sonar_sensor(NXT_PORT_S2);
  ecrobot_init_bt_connection();
}

void ecrobot_device_terminate(void)
{
  nxt_motor_set_speed(MOTOR_STEERING, 0, 1);
  nxt_motor_set_speed(MOTOR_RIGHT, 0, 1);
  nxt_motor_set_speed(MOTOR_LEFT, 0, 1);
  ecrobot_set_light_sensor_inactive(NXT_PORT_S1);
  ecrobot_set_light_sensor_inactive(NXT_PORT_S3);
  ecrobot_term_sonar_sensor(NXT_PORT_S2);
  ecrobot_term_bt_connection();
}

void user_1ms_isr_type2(void)
{
  StatusType ercd;

  ercd = SignalCounter(SysTimerCnt); /* Increment OSEK Alarm Counter */
  if (ercd != E_OK)
  {
    ShutdownOS(ercd);
  }
}

/* TaskInitialize */
TASK(TaskInitialize)
{
  nxt_motor_set_speed(MOTOR_STEERING, 0, 1);
  nxt_motor_set_speed(MOTOR_RIGHT, 0, 1);
  nxt_motor_set_speed(MOTOR_LEFT, 0, 1);
  nxt_motor_set_count(MOTOR_STEERING, 0);
  nxt_motor_set_count(MOTOR_RIGHT, 0);
  nxt_motor_set_count(MOTOR_LEFT, 0);

  EDC_flag = EDC_OFF;

  TerminateTask();
}

/* TaskControl executed every 10msec */
TASK(TaskControl)
{
  S32 analog_stick_left;  /* speed command data from GamePad */
  S32 analog_stick_right; /* steering command data from GamePad */
  S32 steering_angle;
  S32 steering_err;
  S32 steering_speed;
  S32 diff_gain;
  U8 touch_sensor;
  static U8 touch_sensor_state = 0;
  static U8 bt_receive_buf[32]; /* buffer size is fixed as 32 */

  /* receive NXTGamePad command
   * byte0 speed_data -100(forward max.) to 100(backward max.)
   * byte1 steering_data -100(left max.) to 100(right max.)
   */

  ecrobot_read_bt_packet(bt_receive_buf, 32);
  analog_stick_left = -(S32)(*(S8 *)(&bt_receive_buf[0])); /* reverse the direction */
  analog_stick_right = (S32)(*(S8 *)(&bt_receive_buf[1]));

  /* read Touch Sensor to switch Electronic Differential Control */
  touch_sensor = ecrobot_get_touch_sensor(NXT_PORT_S4);
  if (touch_sensor == 1 && touch_sensor_state == 0)
  {
    EDC_flag = ~EDC_flag + 1; /* toggle */
  }
  touch_sensor_state = touch_sensor;

  /* steering control */
  steering_angle = nxt_motor_get_count(MOTOR_STEERING);
  steering_err = (STEERING_LIMIT*analog_stick_right)/100 - steering_angle;
  steering_speed = STEERING_P_GAIN*steering_err;
  nxt_motor_set_speed(MOTOR_STEERING, FrictionComp(steering_speed,PWM_OFFSET), 1);

  /* wheel motors control with Electric Differential Control */
  
diff_gain = 10;
  if (steering_angle > NEUTRAL_DEAD_ZONE) /* turn right */
  {
    if (EDC_flag == EDC_ON)
    {
      diff_gain = (S32)((1.0F - (float)steering_angle*DIFF_GAIN_MAX/STEERING_LIMIT)*10);
    }
    nxt_motor_set_speed(MOTOR_RIGHT,
      FrictionComp((analog_stick_left*diff_gain)/10,PWM_OFFSET), 1);
    nxt_motor_set_speed(MOTOR_LEFT, FrictionComp(analog_stick_left,PWM_OFFSET), 1);
  }
  else if (steering_angle < -NEUTRAL_DEAD_ZONE) /* turn left */
  {
    if (EDC_flag == EDC_ON)
    {
      diff_gain = (S32)((1.0F + (float)steering_angle*DIFF_GAIN_MAX/STEERING_LIMIT)*10);
    }
    nxt_motor_set_speed(MOTOR_RIGHT, FrictionComp(analog_stick_left,PWM_OFFSET), 1);
    nxt_motor_set_speed(MOTOR_LEFT,
      FrictionComp((analog_stick_left*diff_gain)/10,PWM_OFFSET), 1);
  }
  else /* go straight */
  {
    nxt_motor_set_speed(MOTOR_RIGHT, FrictionComp(analog_stick_left,PWM_OFFSET), 1);
    nxt_motor_set_speed(MOTOR_LEFT, FrictionComp(analog_stick_left,PWM_OFFSET), 1);
  }

  /* send NXT status data to NXT GamePad */
  ecrobot_bt_data_logger((S8)analog_stick_left, (S8)analog_stick_right);

  TerminateTask();
}

/* TaskSonar executed every 50msec */
TASK(TaskSonar)
{
  S32 sonar;

  /* Sonar Sensor is invoked just for data logging */
  sonar = ecrobot_get_sonar_sensor(NXT_PORT_S2);

  TerminateTask();
}

/* TaskLCD executed every 500msec */
TASK(TaskLCD)
{
  ecrobot_status_monitor("NXT GT");

  TerminateTask();
}

/* Sub functions */
S32 FrictionComp(S32 ratio, S32 offset)
{
  if (ratio > 0)
  {
    return ((100-offset)*ratio/100 + offset);
  }
  else if (ratio < 0)
  {
    return ((100-offset)*ratio/100 - offset);
  }
  else
  {
    return ratio;
  }
}

samples\nxtgt\nxtgt.oil

#include "implementation.oil"

CPU ATMEL_AT91SAM7S256
{
  OS LEJOS_OSEK
  {
    STATUS = EXTENDED;
    STARTUPHOOK = FALSE;
    ERRORHOOK = FALSE;
    SHUTDOWNHOOK = FALSE;
    PRETASKHOOK = FALSE;
    POSTTASKHOOK = FALSE;
    USEGETSERVICEID = FALSE;
    USEPARAMETERACCESS = FALSE;
    USERESSCHEDULER = FALSE;
  };

  /* Definition of application mode */
  APPMODE appmode1{};

  /* Definition of TaskInitialize */
  TASK TaskInitialize
  {
    AUTOSTART = TRUE
    {
      APPMODE = appmode1;
    };
    PRIORITY = 4;
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 512; /* Stack size */
  };

  /* Definition of TaskControl */
  TASK TaskControl
  {
    AUTOSTART = FALSE;
    PRIORITY = 3;
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 512; /* Stack size */
  };

  /* Definition of TaskSonar */
  TASK TaskSonar
  {
    AUTOSTART = FALSE;
    PRIORITY = 2;
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 512; /* Stack size */
  };

  /* Definition of TaskLCD */
  TASK TaskLCD
  {
    AUTOSTART = FALSE;
    PRIORITY = 1;
    ACTIVATION = 1;
    SCHEDULE = FULL;
    STACKSIZE = 512; /* Stack size */
  };

  /* Definition of OSEK Alarm Counter */
  COUNTER SysTimerCnt
  {
    MINCYCLE = 1;
    MAXALLOWEDVALUE = 10000;
    TICKSPERBASE = 1; /* One tick is equal to 1msec */
  };

  /* Definition of TaskControl execution timing */
  ALARM cyclic_alarm_TaskControl
  {
    COUNTER = SysTimerCnt;
    ACTION = ACTIVATETASK
    {
      TASK = TaskControl;
    };
    AUTOSTART = TRUE
    {
      ALARMTIME = 1;
      CYCLETIME = 10; /* executed every 10msec */
      APPMODE = appmode1;
    };
  };

  /* Definition of TaskSonar execution timing */
  ALARM cyclic_alarm_TaskSonar
  {
    COUNTER = SysTimerCnt;
    ACTION = ACTIVATETASK
    {
      TASK = TaskSonar;
    };
    AUTOSTART = TRUE
    {
      ALARMTIME = 1;
      CYCLETIME = 50; /* executed every 50msec */
      APPMODE = appmode1;
    };
  };

  /* Definition of TaskLCD execution timing */
  ALARM cyclic_alarm_TaskLCD
  {
    COUNTER = SysTimerCnt;
    ACTION = ACTIVATETASK
    {
      TASK = TaskLCD;
    };
    AUTOSTART = TRUE
    {
      ALARMTIME = 1;
      CYCLETIME = 500; /* executed every 500msec */
      APPMODE = appmode1;
    };
  };
};

 

 

 

Back to Samples