NXTLineLeader bugs ?

This is where you talk about the NXJ software itself, installation issues, and programming talk.

Moderators: 99jonathan, roger, imaqine

NXTLineLeader bugs ?

Postby epascual » Sun Jan 17, 2010 12:33 am

Hello,

I've just started to play with my brand new Mindsensors Line leader, and found some strange points in the NXTLineLeader class.

First strange point is that single bytes registers are read as 2 bytes ones. For instance, steering, average, result, Kp,... are read using statements like the following one :
Code: Select all
getData(LL_READ_STEERING, buf, 2)

Shouldn't this be :
Code: Select all
getData(LL_READ_STEERING, buf, 1)

instead ?

In addition, steering is supposed to be signed since it's a bias to be added/subtracted to motors power setting. But in the getSteering method, the returned value is computed using the following expression :
Code: Select all
buf[0] & 0xff

which clears the sign bit and thus makes negative values be returned as positive ones in the upper range of the 0..255 domain.

Using the orginal code makes my basic test program (copied from RobotC equivalent) fail. Patching the source to fix the above mentionned points makes things behave as they should.

Since it's quite surprising nobody ever mentioned these points, I suspect I'm missing something (although I'm rather confident about my fixes). Maybe the code in the lib relates to some previous version of the sensor, since I could notice that some of the sensor features are not made accessible by the class.

Could anybody shed some light please ?

Best regards

PS: in the event my guesses would be right, I would be glad to provide my modified version of the class, with additions supporting missing features of the sensor. Just tell me how I can contribute.
epascual
Active User
 
Posts: 123
Joined: Sun Jan 17, 2010 12:15 am
Location: Sophia-Antipolis (France)

Update NXTLineLeader class

Postby esmetaman » Mon Jan 18, 2010 1:51 pm

Hi friend,

Did you improve the code? I begun to develop the class, but I recognize that I should improve some points. Can you send me your version and One Example to Test.

If the tests goes well, we could update the class in SVN for next LeJOS release.

Please, send me the code to test together

Cheers
Juan Antonio Breña Moral
http://www.juanantonio.info/lejos-ebook/
https://github.com/jabrena/livingrobots
http://www.iloveneutrinos.com/
User avatar
esmetaman
Advanced Member
 
Posts: 311
Joined: Wed Sep 13, 2006 12:16 am
Location: Madrid, Spain

Postby epascual » Mon Jan 18, 2010 10:05 pm

Hello Juan,
Did you improve the code?

I don't know if I improved it, but I modified it :lol:
Can you send me your version and One Example to Test.

Yes of course. I'll do it right now.

Best regards

Eric
epascual
Active User
 
Posts: 123
Joined: Sun Jan 17, 2010 12:15 am
Location: Sophia-Antipolis (France)

Postby esmetaman » Mon Jan 18, 2010 10:47 pm

Hi Eric,

I will test the code this week and I will reply with my comments.

Good job

Cheers
Juan Antonio Breña Moral
http://www.juanantonio.info/lejos-ebook/
https://github.com/jabrena/livingrobots
http://www.iloveneutrinos.com/
User avatar
esmetaman
Advanced Member
 
Posts: 311
Joined: Wed Sep 13, 2006 12:16 am
Location: Madrid, Spain

Postby ChrisB01 » Sat Jan 30, 2010 2:55 pm

I built a class just simply implementing all the functionality in the mindsensor documentation a while ago. Its not fully finished or tested but I have included it in the hope it will be helpful. I have a lineleader so I could help test new code if needed.

Chris

Code: Select all
import lejos.nxt.I2CPort;
import lejos.nxt.I2CSensor;

public class LineLeader extends I2CSensor {
   byte[] buf = new byte[8];

   /**
    * Used by calibrateSensor() to calibrate the white level
    */
   public static final char WHITE = 'W';

   /**
    * Used by calibrateSensor() to calibrate the black level
    */
   public static final char BLACK = 'B';

   /**
    * Used by setSleep() to put sensor to sleep
    */
   public static final char SLEEP = 'D';

   /**
    * Used by setSleep() to wake the sensor up
    */
   public static final char WAKE = 'P';

   /**
    * Used by setLineColor() to reset the color inversion (black line on a
    * white background, this is also the default).
    */
   public static final char NORMAL = 'R';

   /**
    * Used by setLineColor() to invert the colors (White line on a black
    * background)
    */
   public static final char INVERTED = 'I';

   /**
    * Used by setElectricalFrequency() to configure the sensor for US region
    * (and regions with 60 Hz electrical frequency).
    */
   public static final char US = 'A';

   /**
    * Used by setElectricalFrequency() to configure the sensor for European
    * region (and regions with 50 Hz electrical frequency)
    */
   public static final char EU = 'E';

   /**
    * Used by setElectricalFrequency() to configure the sensor for universal
    * frequency (in this mode the sensor adjusts for any frequency, this is
    * also the default mode).
    */
   public static final char U = 'U';

   public LineLeader(I2CPort port) {
      super(port);
   }

   /**
    * Calibrate the sensors white and black values
    *
    * @param calibration
    *            Use the class constants WHITE or BLACK.
    */
   public void calibrateSensor(char calibration) {
      sendCommand(calibration);
   }

   /**
    * To conserve power, the sensor goes to sleep after 1 minute of inactivity.
    * The sensor will wake up on its own when any activity begins. You can also
    * wake it up (or put to sleep) via this command.
    *
    * @param sleep
    *            Use the class constants SLEEP or WAKE.
    */
   public void setSleep(char sleep) {
      sendCommand(sleep);
   }

   /**
    * The sensor can be use with a black or white line on opposite backgrounds,
    * the default is a black line on a white background, use this command to
    * set the line color.
    *
    * @param color
    *            Use the class constants NORMAL or INVERTED.
    */
   public void setLineColor(char color) {
      sendCommand(color);
   }

   /**
    * The sensor can be adjusted for different electrical frequency the default
    * is universal frequency mode, use this command to the electrical
    * frequency.
    *
    * @param color
    *            Use the class constants US ,EU or U.
    */
   public void setElectricalFrequency(char area) {
      sendCommand(area);
   }

   /**
    * Take a snapshot, this command looks at the line under the sensor and
    * stores the width and position of the line in sensor’s memory.
    * Subsequently, sensor will use these characteristics of line to track it.
    * This command inverts the colors if it sees a white line on black
    * background. (PID parameters are not affected).
    */
   public void takeSnapshot() {
      sendCommand('S');
   }

   /**
    *
    * In the simplest form, the sensor will try to maintain line at the center
    * of the sensor. In order to do so, your robot may have to apply different
    * power to left or right motors. This value is provided as the correction
    * needed. You can add this value to your left motor and subtract from right
    * motor. Alternately, you can perform other operations with this value,
    * based on your robot’s and course’s needs.
    *
    * @return the steering value
    */
   public int getSteering() {
      int ret = getData(0x42, buf, 1);
      if (ret != 0)
         return -1;
      return buf[0];
   }

   /**
    * The average is a weighted average of the bits set to 1 based on the
    * position. i.e. left most bit has weight of 10, second bit has weight of
    * 20.
    *
    * @return the average
    */
   public int getAverage() {
      int ret = getData(0x43, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   /**
    * Result is a byte value of the sensor reading. Each bit corresponding to
    * the sensor where the line is seen is set to 1, or else the bit is zero.
    *
    * @return the result
    */
   public int getResult() {
      int ret = getData(0x44, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   /**
    * The Set Point is a value you can ask sensor to maintain the average to.
    * The default value is 45, whereby the line is maintained in center of the
    * sensor. If you need to maintain line towards left of the sensor, set the
    * Set Point to a lower value (minimum: 10). If you need it to be towards on
    * the right of the sensor, set it to higher value (maximum: 80). Set point
    * is also useful while tracking an edge of dark and light areas.
    *
    * @return the set point
    */
   public int getSetPoint() {
      int ret = getData(0x45, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   public int getKp() {
      int ret = getData(0x46, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   public int getKi() {
      int ret = getData(0x47, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   public int getKd() {
      int ret = getData(0x48, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   public int getKpDivisor() {
      int ret = getData(0x61, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   public int getKiDivisor() {
      int ret = getData(0x62, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   public int getKdDivisor() {
      int ret = getData(0x63, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   public int setSetPoint(int point) {
      return sendData(0x45, (byte) point);
   }

   public int setKp(int kp) {
      return sendData(0x46, (byte) kp);
   }

   public int setKi(int ki) {
      return sendData(0x47, (byte) ki);
   }

   public int setKd(int kd) {
      return sendData(0x48, (byte) kd);
   }

   public int setKpDivisor(int divisor) {
      return sendData(0x61, (byte) divisor);
   }

   public int setKiDivisor(int divisor) {
      return sendData(0x62, (byte) divisor);
   }

   public int setKdDivisor(int divisor) {
      return sendData(0x63, (byte) divisor);
   }

   public int getSensorValue(int id) {
      if (id <= 0 || id > 8)
         return -1;
      int ret = getData(0x48 + id, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   public int getWhiteLimit(int id) {
      if (id <= 0 || id > 8)
         return -1;
      int ret = getData(0x50 + id, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   public int getBlackLimit(int id) {
      if (id <= 0 || id > 8)
         return -1;
      int ret = getData(0x58 + id, buf, 1);
      if (ret != 0)
         return -1;
      return (0xFF & buf[0]);
   }

   /**
    * Send a single byte command represented by a letter
    *
    * @param cmd
    *            the letter that identifies the command
    */
   public void sendCommand(char cmd) {
      sendData(0x41, (byte) cmd);
   }

}
ChrisB01
Advanced Member
 
Posts: 189
Joined: Sat Mar 15, 2008 12:19 pm
Location: UK

Postby Walt White » Sun Jan 16, 2011 9:46 pm

Chris,

Thanks for creating this LineLeader class. I couldn't figure out how to use the NXTLineLeader class from the leJOS 0.8.5 classes.jar, but your class seems to be much more complete. I also like the inclusion of Divisors which allow the "K" terms to be integers when the desired effect is a float term.

I've proposed that there be an edge following Pursuit contest at Bricks By the Bay 2011 in Santa Clara, California, this coming March. I built a robot using three RCX light sensors and it worked OK, but I think the NXTLineLeader sensor will be better.

In case other beginners wonder how to use this class, the code below calibrates the WHITE and BLACK values then displays the Steering, Result, and Average values using the sensor's default "K" and Divisor values. I expect that I will be able to use the Steering value when I write the PID program that uses this class.
Code: Select all
     
     import lejos.nxt.*;
     
     public class DisplayOnly {
        
        static LineLeader liner = new LineLeader(SensorPort.S3);
        
        public static void main(String[] args) throws InterruptedException {
           DisplayOnly display = new DisplayOnly();
        }
        
        public DisplayOnly() throws InterruptedException  {
           liner.setSleep('P');  // wake it up; turn it on.
     //      liner.setSetPoint(100);
           LCD.drawString("Light settings: ", 0, 0);
           LCD.drawString("Place robot over", 0, 1);
           LCD.drawString("white area ", 0, 2);
           LCD.drawString("Press orange btn", 0, 3);
           Button.ENTER.waitForPressAndRelease();
           LCD.drawString("**** wait ******", 0, 3);
           liner.calibrateSensor('W');
           wait(150);
           LCD.clear();
           LCD.drawString("Place robot over", 0, 4);
           LCD.drawString("black area ", 0, 5);
           LCD.drawString("Press orange btn", 0, 6);
           Button.ENTER.waitForPressAndRelease();
           LCD.drawString("**** wait ******", 0, 6);
           liner.calibrateSensor('B');
           LCD.clear();
           LCD.drawString("SetPoint=" + liner.getSetPoint(), 0, 0);
           LCD.drawString("Kp=" + liner.getKp() + " " + "KpDiv=" + liner.getKpDivisor(), 0, 1);
           LCD.drawString("Ki=" + liner.getKi() + " " + "KiDiv=" + liner.getKiDivisor(), 0, 2);
           LCD.drawString("Kd=" + liner.getKd() + " " + "KdDiv=" + liner.getKdDivisor(), 0, 3);
           LCD.drawString("Steering = ", 0, 5);
           LCD.drawString("Result   = ", 0, 6);
           LCD.drawString("Average  = ", 0, 7);
           
           while (!Button.ESCAPE.isPressed())
           {
              LCD.drawString("      ", 11, 5);
              LCD.drawString("      ", 11, 6);
              LCD.drawString("      ", 11, 7);
              LCD.drawInt(liner.getSteering(), 11, 5);
              LCD.drawInt(liner.getResult(), 11, 6);
              LCD.drawInt(liner.getAverage(), 11, 7);
              wait(100);
           }
        }   
        
         public static void wait(int milliseconds) throws InterruptedException {
           Thread.sleep(milliseconds);
        }
     }      

The display shows:

SetPoint=45
Kp=25 KpDiv=32
Ki=0 KiDiv=32
Kd=10 KdDiv=32

The display shows (using 'code' view below for better formatting) the following values for the conditions listed:
Code: Select all
           Over       Half white    Over
           all white  half black    all black
=============================================
Steering=  100           15          -100
Result=    0             240         255
Average=   0             65          0

When I set the SetPoint to a value of 100, the display shows:
Code: Select all
           Over       Half white    Over
           all white  half black    all black
=============================================
Steering=  -100          -27         100
Result=    0             240         255
Average=   0             65          0

Programming and tuning PID control is difficult enough. Thanks to your class, I now have a chance to make it work.

Walt
Walt White
Novice
 
Posts: 44
Joined: Sun Aug 06, 2006 11:57 pm
Location: California Central Valley


Return to NXJ Software

Who is online

Users browsing this forum: Yahoo [Bot] and 3 guests

more stuff