HITechnic (Segway source code - 01)

simpled 2013.06.24 10:43 조회 수 : 1338

//=====================================================================
// HiTechnic HTWay Version 1.1
// Copyright (c) 2010 HiTechnic
//
// Revision History
//   1.1    Fixed a bug that prevented the HTWayC program from working
//          correctly when optimization level was set to 2.
//          Also imporived the accuracy of the initial gyro offset
//          caluculation by ensuring the motor controller in active.
//
// Gyro based sample Segway type robot. This NXC program uses the LEGO
// 2.0 firmware which has floating point support.  It has been tested
// with LEGO NXT firmware 1.29.
//
// To use the floating point math available in the 2.0 firmware,
// BricxCC must be setup to tell the compiler to target that firmware.
// From the Edit menu, select Preferences.  Select the Compiler tab,
// and then NBC/NXC sub-tab and check 'NXT 2.0 compatible firmware.'

// Port Input and Output
#define GYRO         IN_1
#define IR_RECEIVER  IN_4

#define LEFT_MOTOR   OUT_C
#define RIGHT_MOTOR  OUT_A
#define MOTORS       OUT_AC

//=====================================================================
// Balancing constants
//
// These are the constants used to maintain balance.
//
// Loop wait time.  WAIT_TIME is the time in ms passed to the
// Wait command.
#define WAIT_TIME    8

// Wheel ratio is relative size compared to NXT 1.0 wheels
// For the NXT 1.0 wheels (56x26) use:           1.0
// For the NXT 2.0 wheels (43.2x22) use:         0.7
// For the big white RCX wheels (81.6x15) use:   1.4
#define WHEEL_RATIO_NXT1 1.0
#define WHEEL_RATIO_NXT2 0.8
#define WHEEL_RATIO_RCX  1.4

// These are the main four balance constants, only the gyro
// constants are relative to the wheel size.  KPOS and KSPEED
// are self-relative to the wheel size.
#define KGYROANGLE  7.5
#define KGYROSPEED  1.15
#define KPOS        0.07
#define KSPEED      0.1

// This constant aids in drive control.  When the robot starts
// moving because of user control, this constant helps get the
// robot leaning in the right direction.  Similarly, it helps
// bring robot to a stop when stopping.
#define KDRIVE      -0.02

// Power differential used for steering based on difference of
// target steering and actual motor difference.
#define KSTEER      0.25

// Gyro offset control
// The gyro sensor will drift with time.  This constant is used in a
// simple long term averaging to adjust for this drift.
// Every time through the loop, the current gyro sensor value is
// averaged into the gyro offset weighted according to this constant.
#define EMAOFFSET 0.0005

// If robot power is saturated (over +/- 100) for over this time limit
// then robot must have fallen.  In milliseconds.
#define TIME_FALL_LIMIT     1000

//---------------------------------------------------------------------
// IR Control constants
#define CONTROL_WAIT 25

// To use a different IR channel, chage these constants
#define IR_LEFT           HT_CH1_A
#define IR_RIGHT          HT_CH1_B

// This constant is in degrees/second for maximum speed.  Note that
// position and speed are measured as the sum of the two motors, in
// other words, 600 would actually be 300 degrees/second for each
// motor.
#define CONTROL_SPEED  600.0

//=====================================================================
// Global variables
//
// These variables are used to control the movement of the robot.  Both
// are in degrees/second.
//
// motorControlDrive is the target speed for the sum of the two motors
// while motorControlSteer is the target change in difference for two motors.
float motorControlDrive = 0.0;
float motorControlSteer = 0.0;

// This global contains the target motor differential, essentially,
// which way the robot should be pointing.  This value is
// updated every time through the balance loop based on motorControlSteer
float motorDiffTarget = 0.0;

// Time that robot first starts to balance.  Used to calculate tInterval
long tCalcStart;

// tInterval is the time, in seconds, for each iteration of the
// balance loop.
float tInterval;

// ratioWheel stores the relative wheel size compared to a standard NXT
// 1.0 wheel.  RCX 2.0 wheel has ratio of 0.7 while large RCX wheel is 1.4
float ratioWheel;

// Gyro globals
float gOffset;
float gAngleGlobal = 0;

// Motor globals
float motorPos = 0;
long mrcSum = 0, mrcSumPrev;
long motorDiff;
long mrcDeltaP3 = 0;
long mrcDeltaP2 = 0;
long mrcDeltaP1 = 0;


//=====================================================================
// void SetWheelRatio()
//
// This function implements a UI so that the user can choose the
// size of the wheels used on the robot.
void SetWheelRatio()
{
  int nSelect;

  // Start of with Medium (NXT 1.0) selected
  nSelect = 2;

  do {
    // Display base text of user interface on screen
    ClearScreen();
    TextOut(0, LCD_LINE1, "HiTechnic-HTWayC");
    TextOut(6, LCD_LINE2, "Set wheel size:");
    TextOut(2, LCD_LINE8, "<   [Select]   >");

    // Display current selection
    switch (nSelect) {
    case 1:
      // Small
      CircleOut(50, 32, 6);
      TextOut(7, LCD_LINE7, "Small (NXT 2.0)");
      ratioWheel = WHEEL_RATIO_NXT2;
      break;
    case 2:
      // Medium
      CircleOut(50, 32, 10);
      TextOut(2, LCD_LINE7, "Medium (NXT 1.0)");
      ratioWheel = WHEEL_RATIO_NXT1;
      break;
    case 3:
      // Large
      CircleOut(50, 32, 14);
      TextOut(17, LCD_LINE7, "Large (RCX)");
      ratioWheel = WHEEL_RATIO_RCX;
      break;
    }

    // Make sure previous button is released, loop until all released
    while(ButtonPressed(BTNLEFT, false) ||
          ButtonPressed(BTNCENTER, false) ||
          ButtonPressed(BTNRIGHT, false));

    // Get button press
    while(true) {
      if (ButtonPressed(BTNLEFT, false)) {
        nSelect--;
        if (nSelect < 1) nSelect = 3;
        break;
      }
      if (ButtonPressed(BTNRIGHT, false)) {
        nSelect++;
        if (nSelect > 3) nSelect = 1;
        break;
      }
      if (ButtonPressed(BTNCENTER, false))
        break;
    }
  } until (ButtonPressed(BTNCENTER, false));
  ClearScreen();
}

//=====================================================================
// GetGyroOffset
//
// This function returns a suitable initial gyro offset.  It takes
// 100 gyro samples over a time of 1/2 second and averages them to
// get the offset.  It also check the max and min during that time
// and if the difference is larger than one it rejects the data and
// gets another set of samples.
#define OFFSET_SAMPLES 100

void GetGyroOffset()
{
  float gSum;
  int  i, gMin, gMax, g;

  ClearScreen();
  TextOut(0, LCD_LINE1, "HiTechnic-HTWayC");

  TextOut(0, LCD_LINE4, "Lay robot down");
  TextOut(0, LCD_LINE5, "flat to get gyro");
  TextOut(0, LCD_LINE6, "offset.");
  
  // Ensure that the motor controller is active since this affects
  // the gyro values.
  Off(MOTORS);
  
  do {
    gSum = 0.0;
    gMin = 1000;
    gMax = -1000;
    for (i=0; i<OFFSET_SAMPLES; i++) {
      g = SensorHTGyro(GYRO);

      if (g > gMax)
        gMax = g;
      if (g < gMin)
        gMin = g;

      gSum += g;
      Wait(5);
    }
  } while ((gMax - gMin) > 1);   // Reject and sample again if range too large

  //Average the sum of the samples.
  gOffset = gSum / OFFSET_SAMPLES + 1.0;
  
  // Even with motor controller active, the initial offset appears to
  // be off from the actual needed offset to keep robot from wondering.
  // This +1 helps keep robot from wondering when it first starts to
  // balance.
}

//=====================================================================
void StartBeeps()
{
  int c;

  ClearScreen();
  TextOut(0, LCD_LINE1, "HiTechnic-HTWayC");

  TextOut(20, LCD_LINE3, "Balance in");

  // Play warning beep sequence to indicate balance about to start
  for (c=5; c>0;c--) {
    NumOut(47, LCD_LINE4, c);
    PlayTone(440,100);
    Wait(1000);
  }
  NumOut(47, LCD_LINE4, 0);
  PlayTone(440,1000);
  Wait(1000);
}

//=====================================================================
// task main - initializes the sensors, Both taskBalance and
// taskController start when main ends.
task main()
{
  // Initialize the sensors
  SetSensorHTGyro(GYRO);
  SetSensorLowspeed(IR_RECEIVER);
  Wait(50);

  // Get user input on wheel size
  SetWheelRatio();

  // Get the initial gyro offset
  GetGyroOffset();

  // Play warning beep sequence before balance starts
  StartBeeps();

  // When task main ends, both taskBalance and taskControl will start
}

//=====================================================================
// GetGyroData(float &gyroSpeed, float &gyroAngle)
//
// Get the data from the gyro.
// Fills the pass by reference gyroSpeed and gyroAngle based on
// updated information from the Gyro Sensor
//
// Maintains an automatically adjusted gyro offset as well as
// the integrated gyro angle.
inline void GetGyroData(float &gyroSpeed, float &gyroAngle)
{
  float gyroRaw;

  gyroRaw = SensorHTGyro(GYRO);
  gOffset = EMAOFFSET * gyroRaw + (1-EMAOFFSET) * gOffset;
  gyroSpeed = gyroRaw - gOffset;

  gAngleGlobal += gyroSpeed*tInterval;
  gyroAngle = gAngleGlobal;
}

//---------------------------------------------------------------------

inline void GetMotorData(float &motorSpeed, float &motorPos)
{
  long mrcLeft, mrcRight, mrcDelta;

  // Keep track of motor position and speed
  mrcLeft = MotorRotationCount(LEFT_MOTOR);
  mrcRight = MotorRotationCount(RIGHT_MOTOR);

  // Maintain previus mrcSum so that delta can be calculated and get
  // new mrcSum and Diff values
  mrcSumPrev = mrcSum;
  mrcSum = mrcLeft + mrcRight;
  motorDiff = mrcLeft - mrcRight;

  // mrcDetla is the change int sum of the motor encoders, update
  // motorPos based on this detla
  mrcDelta = mrcSum - mrcSumPrev;
  motorPos += mrcDelta;

  // motorSpeed is based on the average of the last four delta's.
  motorSpeed = (mrcDelta+mrcDeltaP1+mrcDeltaP2+mrcDeltaP3)/(4*tInterval);

  // Shift the latest mrcDelta into the previous three saved delta values
  mrcDeltaP3 = mrcDeltaP2;
  mrcDeltaP2 = mrcDeltaP1;
  mrcDeltaP1 = mrcDelta;
}

//---------------------------------------------------------------------
// void SteerControl(int power, int &powerLeft, int &powerRight)
//
// This function determines the left and right motor power that should
// be used based on the balance power and the steering control
inline void SteerControl(int power, int &powerLeft, int &powerRight)
{
  int powerSteer;

  // Update the target motor difference based on the user steering
  // control value.
  motorDiffTarget += motorControlSteer * tInterval;

  // Determine the proportionate power differential to be used based
  // on the difference between the target motor difference and the
  // actual motor difference.
  powerSteer = KSTEER * (motorDiffTarget - motorDiff);

  // Apply the power steering value with the main power value to
  // get the left and right power values.
  powerLeft = power + powerSteer;
  powerRight = power - powerSteer;

  // Limit the power to motor power range -100 to 100
  if (powerLeft > 100)   powerLeft = 100;
  if (powerLeft < -100)  powerLeft = -100;

  // Limit the power to motor power range -100 to 100
  if (powerRight > 100)  powerRight = 100;
  if (powerRight < -100) powerRight = -100;
}

//---------------------------------------------------------------------
// void CalcInterval(long cLoop)
//
// Calculate the interval time from one iteration of the loop to the next.
// Note that first time through, cLoop is 0, and has not gone through
// the body of the loop yet.  Use it to save the start time.
// After the first iteration, take the average time and convert it to
// seconds for use as interval time.
inline void CalcInterval(long cLoop)
{
  if (cLoop == 0) {
    // First time through, set an initial tInterval time and
    // record start time
    tInterval = 0.0055;
    tCalcStart = CurrentTick();
  } else {
    // Take average of number of times through the loop and
    // use for interval time.
    tInterval = (CurrentTick() - tCalcStart)/(cLoop*1000.0);
  }
}

//---------------------------------------------------------------------
// taskBalance
// This is the main balance task for the HTWay robot.
//
// Robot is assumed to start leaning on a wall.  The first thing it
// does is take multiple samples of the gyro sensor to establish and
// initial gyro offset.
//
// After an initial gyro offset is established, the robot backs up
// against the wall until it falls forward, when it detects the
// forward fall, it start the balance loop.
//
// The main state variables are:
// gyroAngle  This is the angle of the robot, it is the results of
//            integrating on the gyro value.
//            Units: degrees
// gyroSpeed  The value from the Gyro Sensor after offset subtracted
//            Units: degrees/second
// motorPos   This is the motor position used for balancing.
//            Note that this variable has two sources of input:
//             Change in motor position based on the sum of
//             MotorRotationCount of the two motors,
//            and,
//             forced movement based on user driving the robot.
//            Units: degrees (sum of the two motors)
// motorSpeed This is the speed of the wheels of the robot based on the
//            motor encoders.
//            Units: degrees/second (sum of the two motors)
//
// From these state variables, the power to the motors is determined
// by this linear equation:
//     power = KGYROSPEED * gyro +
//             KGYROANGLE * gyroAngle +
//             KPOS       * motorPos +
//             KSPEED     * motorSpeed;
//
task taskBalance()
{
  Follows(main);

  float gyroSpeed, gyroAngle;
  float motorSpeed;

  int power, powerLeft, powerRight;
  long tMotorPosOK;
  long cLoop = 0;

  ClearScreen();
  TextOut(0, LCD_LINE1, "HiTechnic-HTWayC");
  TextOut(0, LCD_LINE4, "Balancing");

  tMotorPosOK = CurrentTick();

  // Reset the motors to make sure we start at a zero position
  ResetRotationCount(LEFT_MOTOR);
  ResetRotationCount(RIGHT_MOTOR);

  while(true) {
     CalcInterval(cLoop++);

     GetGyroData(gyroSpeed, gyroAngle);

     GetMotorData(motorSpeed, motorPos);

     // Apply the drive control value to the motor position to get robot
     // to move.
     motorPos -= motorControlDrive * tInterval;

     // This is the main balancing equation
     power = (KGYROSPEED * gyroSpeed +               // Deg/Sec from Gyro sensor
              KGYROANGLE * gyroAngle) / ratioWheel + // Deg from integral of gyro
             KPOS       * motorPos +                 // From MotorRotaionCount of both motors
             KDRIVE     * motorControlDrive +        // To improve start/stop performance
             KSPEED     * motorSpeed;                // Motor speed in Deg/Sec

     if (abs(power) < 100)
       tMotorPosOK = CurrentTick();

     SteerControl(power, powerLeft, powerRight);

     // Apply the power values to the motors
     OnFwd(LEFT_MOTOR, powerLeft);
     OnFwd(RIGHT_MOTOR, powerRight);

     // Check if robot has fallen by detecting that motorPos is being limited
     // for an extended amount of time.
     if ((CurrentTick() - tMotorPosOK) > TIME_FALL_LIMIT) {
       break;
     }

     Wait(WAIT_TIME);
  }
  Off(MOTORS);

  TextOut(0, LCD_LINE4, "Oops... I fell");

  TextOut(0, LCD_LINE8, "tInt ms:");
  NumOut(6*8, LCD_LINE8, tInterval*1000);
}

http://hdh7485.blog.me/20122349172

//=====================================================================
// taskControl
// This task runs in parallel to taskBalance.  This task monitors
// the IR Receiver and sets the global variables motorControlDrive
// and motorControlSteer.  Both of these values are in degrees/second.
//
task taskControl()
{
  Follows(main);

  char pfData[8];

  while(true) {
    ReadSensorHTIRReceiver(IR_RECEIVER, pfData);
    if (pfData[IR_LEFT] == -128) pfData[IR_LEFT] = 0;
    if (pfData[IR_RIGHT] == -128) pfData[IR_RIGHT] = 0;

    // Set control Drive and Steer.  These are in motor degree/second
    motorControlDrive = (pfData[IR_LEFT] + pfData[IR_RIGHT]) * CONTROL_SPEED / 200.0;
    motorControlSteer = (pfData[IR_LEFT] - pfData[IR_RIGHT]) * CONTROL_SPEED / 200.0;

    Wait(CONTROL_WAIT);
  }
  // Wait to allow user time to see screen.
  Wait(10000);
}