Saturday, 17 January 2009

End-project. PART XI

Date: 17 01 2009
Duration of activity: 3 hours
Group members participating: all group members

1. The Goal

The goal of this lab session was to further test drawing and adjust the code in order to improve accuracy.

2. The Plan

  • Make adjustments to the structure: The pen-holder and the working area surface.

  • Test drawing.

  • Make some plots with respect to tacho count changes.

  • Make further adjustments to the code.

3. The Results

3.1. Reconstruction

The LEGO-buildup got some reconstructions along the way. First of all, the NXT holder was put into a more appropriate position.

Due to the Z-axis issue, a pen-holder was constructed, that can move up and down independently of the Z-axis. Is is designed with an elastics-based load in mind, but it may be used with only the help of gravity, too (pen type permitting, of course).

To keep the paper sheet stabilized, two paper holders were constructed:

So the overall look of our robot structure at this moment can be seen here:

3.2. Drawing

This lab session also included some drawing experiments. In the picture, there are two triangles. The black one is as imprecise as it is because the pen was pushed with too much force onto the paper and the motors could not compensate for that pressure.

3.3. Plotting

One more important thing that was done during this lab session, was to plot how motor power works with respect to gathering tacho count values. The idea is to see what kind of change in tacho count that the motor gives back at all the different speeds (here, sampled over a Thread.sleep(1000). There is a slight difference in values for x-axis and for y-axis. Nevertheless, it takes more than 50 of motor power to make the motor start to move and, therefore, for tacho values to change.

This information is paramount to defining NavigationLayerImp's motorPowerFromFraction() method, which basically must map a fraction of the maximal speed to the motor power required to obtain that speed. Assuming the plot to be linear, a linear mapping can be defined and inverted, and, as of writing, the power required is calculated as power = Math.round(45.0f * fraction) + 55;. Note that the code assumes both axes to behave similarly, which, based on the graphs, is not the worst assumption.

3.4. Motor layer

  • MotorSpeed() now checks if a carriage starts at the border (and makes a very error-ish sound if it is)

  • getEndpointThreshold(MotorPort motor) returns the constant endpoint-threshold associated with the axis of a motor. Is private.

  • cacheSpeed(MotorPort motor, int speed) caches the speed that is given as a parameter of the axies associated with the motor.

3.5. Calibration layer

  • findMinAndMax() now clears and re-sets the values of min and max tacho count.

3.6. Navigation layer

  • motorPowerFromFraction (float fraction) returns the power to supply to a motor to obtain the desired fraction of the maximal speed and it was updated to work more properly. See the above discussion.

  • motorPowerFromDistance (float[] distance) It is not the responsibility of this method to do over-compensation in one axis, since this is implicitly handled by the fact that the distance-input will want to move in an angle that (in the out-of-place-situation) does not express the scheduled angle for the given point in time, but rather, the angle towards the current supposed-to-be-at position. Note that there's a difference, here. All in all, this method must only implement the give-motor-powers-from-distance semantic, but that this also involves an urgency factor, the accel's. Since this method is such a neat way (in the author's mind) for the NavigationLayerImp to do corrective action, it is replicated below, where all of the discussed features can be seen in use.

  • public int[] motorPowerFromDistance (float[] distance)
    int retval[] = {0,0,0};
    float magnitude = (float)Math.sqrt(distance[0]*distance[0]+distance[1]*distance[1]);
    float unit_vector[] = {distance[0] / magnitude,distance[1] / magnitude};
    float accel_vector[] = {0f,0f};

    /* Adjust the acceleration factor according to the absolute distance
    * from the desired point. Mostly a linear mapping, but with a cutoff at
    * the bottom and a maximal output. */
    for(int i = 0; i < 2; i++)
    if(Math.abs(distance[i]) <= ACCELERATION_MINPOINT)
    accel_vector[i] = 0.0f;
    accel_vector[i] = (float)Math.abs(distance[i]) / ACCELERATION_FACTOR;

    retval[0] = motorPowerFromFraction(unit_vector[0] * accel_vector[0]);
    retval[1] = motorPowerFromFraction(unit_vector[1] * accel_vector[1]);

    /* Handle the Z axis separately. Basically, always use the slowest speed
    * available -- but only so when needed. */
    if (Math.abs(distance[2]) <= Z_AXIS_INSIGNIFICANCE_THRESHOLD)
    retval[2] = 0;
    retval[2] = (distance[2] > 0f)?MINIMAL_Z_POWER_DOWN:-MINIMAL_Z_POWER_UP;
    return retval;

3.7. Gradient interpreter layer


  • assureMaintainerRunning() has been changed so that the operation only becomes pop'ed after it was actually carried out. This is required to distinguish emptiness of the queue.

  • timeNeededForMovement(float[] from, float[] to) calculates approximate times to go from one coordinate to the other in a straight line (basically, which is self-explanatory, this is the highest axis-wise time requirement, which sets the lower bound on the time duration of the whole movement):

    public int timeNeededForMovement(float[] from, float[] to)
    float max_speeds[] = nav_layer.getMaxVelocity();
    int timereq_x = Math.round((float)Math.abs(to[0]-from[0]) / max_speeds[0]),
    timereq_y = Math.round((float)Math.abs(to[1]-from[1]) / max_speeds[1]),
    timereq_z = Math.round((float)Math.abs(to[2]-from[2]) / max_speeds[2]);
    return Math.max(timereq_x,Math.max(timereq_y,timereq_z));

  • cartesianDistance(float[] from, float[] to) calculates the cartesian distance between two coordinates. This takes into account all of the coordinates, and takes steps to conserve numerical accuracy.

  • reZero() has this sequence of work: initiates the move back to origo; waits for the queue of operations to become empty; initiates the move back to where the reZero() was called from.

4. Conclusion

This lab session showed that we are able to draw now even a triangle quite precisely. This is due to the minor structure modifications (in particular the new Z-axis-independent pen-mount) and adjustments to the code, especially to the navigation layer. What is more, plotting of experiments showed the relationship between the motor power and tacho count value-changes: It is quite linear.

No comments: