New (unofficial) library for wheels and gripper

Greetings,

The official Sparki library has limitations that prevented me from implementing the plans I had for my group of middle school kids. My solution to this problem was to rewrite the library code that controls the motors (wheels and gripper) of Sparki.

If anybody else wants to use this code, feel free. The old motor control functions (e.g. moveForward(), moveRight(), gripperOpen() etc.) still work, but if you instead use my new functions, they will bring you some benefits, such as
[ul]1) mechanisms to control driving speed.
2) functions for making a turn by driving an arc (as opposed to spinning in place).
3) a method for improving accuracy of driving distances and turn angles by specifying the wheel properties of your particular Sparki.
4) better control of the gripper.
[/ul]
The following GitHub repository is the latest version of Arcbotics’ Sparki repo plus my changes:
github.com/robotobyte/Sparki/tr … %20Library

The only files that I modified are:
Sparki.h: github.com/robotobyte/Sparki/bl … y/Sparki.h
Sparki.cpp: github.com/robotobyte/Sparki/bl … Sparki.cpp

Whenever Arcbotics releases a new, official library update, I will attempt to merge my changes with theirs, but no promises.

Below is more information on what I did. My write-up is not intended to be a detailed user’s or teaching guide, so your mileage will vary.

If you run into bugs, please let me know, and I will fix them.

Legacy Motor Control Interface:

The original motor control functions (e.g. moveForward(), moveRight(), gripperOpen() etc.) still work, but I’ll be using my new motor control functions from hereon. First, the new functions provide additional features. Second, the old move*() functions use floating point numbers which are expensive in terms of computation time and code size. The new motor control code (except for the legacy functions) does not use floating point anywhere (although some other non-motor functions in the library still use floating point).

Primary Motor Control Interface:

driveForward(), driveBackward()

driveForward() and driveBackward() replace moveForward() and moveBackward(), respectively. Sparki will drive straight forward or backward. Note that the drive*() functions take distances in millimeters whereas the move*() functions took their distances in centimeters (1cm=10mm). Also, unlike the move*() functions, the drive*() functions let you play with motor speed (see speedPercent parameter).

Function interface definition:

[code] void driveForward (
int16_t distanceToDriveMm = 0,
boolean waitUntilDone = true,
uint8_t speedPercent = -1
);

void driveBackward (
  int16_t distanceToDriveMm = 0,
  boolean waitUntilDone = true,
  uint8_t speedPercent = -1
);[/code]

As with the legacy functions, a distance of zero actually means drive forever, although this behavior can be changed by setting

so that driving a distance (or spinning an angle) of zero means do nothing.

Note that the waitUntilDone parameter defaults to true unless the distance is zero. If the distance is zero or waitUntilDone is set to false, then the drive*() function will be non-blocking. The motors will start and code after the drive*() function will continue execution right away.

The default of -1 for speedPercent means that the speed will be taken from the sparki.wheelSpeedPercentDefault variable. This variable is initially set to 80 but can be changed as in

Examples:

[code] sparki.driveForward ( 50 );
sparki.driveForward ( 50, true );
// drives forward 50mm (or 5cm) at default speed; following
// code is only executed once the 50mm drive is done

sparki.driveForward ( 80, false );
// drives forward 80mm at default speed; following code is
// executed immediately, even if motors are still running

sparki.driveForward ( 80, true, 100 );
// drives forward 100mm at 100% speed; following code is
// executed only after the drive is done

sparki.driveForward ();
sparki.driveForward ( 0 );
// drives forward at default speed and keeps going until
// stopped with stopWheels() (see below); does nothing if
// sparki.distanceOfZeroMeansInfinity = false[/code]

spinLeft(), spinRight()

spinLeft() and spinRight() cause Sparki to spin in place and replace moveLeft() and moveRight(), respectively. The spin amount is specified in degrees (as it was with the move*() functions, although now it’s only an integer). The waitUntilDone and speedPercent parameters work like they do with the drive*() functions.

void spinLeft ( int16_t distanceToSpinDeg = 0, boolean waitUntilDone = true, uint8_t speedPercent = SPARKI_MOTOR_DEFAULT_SPEED_PERCENT ); void spinRight ( int16_t distanceToSpinDeg = 0, boolean waitUntilDone = true, uint8_t speedPercent = SPARKI_MOTOR_DEFAULT_SPEED_PERCENT );

turnLeft(), turnRight()

With turnLeft() and turnRight(), Sparki will drive a turn with a particular angle and radius.

void turnLeft ( int16_t angleToTurnDeg = 90, int16_t radiusOfTurnMm = SPARKI_TURN_RADIUS_DEFAULT_MM, boolean waitUntilDone = true, int8_t speedPercent = -1 ); void turnRight ( int16_t angleToTurnDeg = 90, int16_t radiusOfTurnMm = SPARKI_TURN_RADIUS_DEFAULT_MM, boolean waitUntilDone = true, int8_t speedPercent = -1 );

The default turn angle is 90 degrees. The turn radius is relative to Sparki’s pen hole, and the default radius is currently set to 75mm.

Examples:

[code] sparki.turnRight ( 360 );
// drives a circle clockwise, and with a pen will
// draw a 75mm radius (150mm diameter) circle

sparki.turnLeft ( 180, 100 );
// makes a counter-clockwise U-turn with a 100mm
// (10cm) turn radius[/code]

wheelsAreRuning(), stopWheels()

The wheelsAreRunning() function may be used to test whether the wheels are still turning from a prior, non-blocking drive*(), spin*() or turn*() (i.e. a drive, spin or turn with waitUntilDone set to false).

Example:

openGripper(), closeGripper(), setGripperSpacing()

openGripper() may be used to open the gripper fully or open it by a specific number of millimeters. At most, the gripper will run long enough to open it from a fully closed position. closeGripper() works like openGripper() but in the closing direction.

Once the gripper has been fully opened or closed, setGripperSpacing() may be used to set spacing between the grippers to a specific number of millimeters. If the gripper is somehow prevented from closing or opening to the commanded position (e.g. if the gripper is set to close to 20mm but it’s grabbing a 30mm object), the subsequent calls to setGripperSpacing() will not yield accurate results until the gripper is once again fully opened or closed.

Without arguments, the openGripper() function will run the gripper long enough to open it fully if it was previously fully closed.

Examples:

[code] sparki.openGripper ();
// opens the gripper fully and calibrates its position

sparki.closeGripper ( 10 );
// closes the gripper by 10mm (i.e. space between
// grippers will become 10mm less than it is)

sparki.setGripperSpacing ( 25 );
// moves the gripper, so that the separation will be
// 25mm (assuming the gripper position calibration
// is valid)[/code]

setWheelDiameter(), setWheelSeparation ()

Driving distance and spin angles are calibrated for a wheel diameter (outer diameter of the black o-rings around the wheels) of 51.0mm and a wheel-to-wheel distance (o-ring to o-ring) of 85.5mm. If your Sparki is a little different, then drive distances and spin angles will not be quite right. You can tune the behavior by using the setWheelDiameter() and setWheelSeparation() functions.

void setWheelDiameter ( uint16_t wheelDiameterUm ); void setWheelSeparation ( uint16_t wheelSeparationUm );

Call either or both of these functions before performing any driving or spinning actions. For either function, the argument is in tenths of millimeters (100=10.0mm).

Example:

setWheelSeparation ( 860 ); // sets wheel separation to 860 mm/10 or 86.0mm.

Thanks, this is really great work!

Jerry

Thanks for the comment, Jerry.

Have you actually been able to try the library and did you have some success with it?

Thanks, this is great…Arcbotics should seriously consider making these as part of the official release, which by the way is long overdue. We have been promised week after week but have not seen any action. It’s quite hard to implement some codes if the fundamental library are bug ridden or not fully implemented.

wolfw:

Yes, I have used your version and it seems to work much better so far. I haven’t had time to test it extensively but it a big improvement. I was also happy with your discovery about the interrupt period in another post - I was about ready to get my scope out to look at that but you saved me trouble - thanks. It is great to see people digging in to help make Sparki a better robot. Software is tough to get right the first time and can nearly always be improved. They created a nice platform and we can all make it better.

Jerry

WolfW,

Thanks very much for the updates! They indeed perform better than the standard functions. I’ve attached a photo showing the comparison of the Arcbotics code and your code, each drawing a 20cm square.

The writeup was excellent as well. I’d add only that 1mm = 1000um, and that wheelSeparation and wheelDiameter are specified in 10ths of a millimeter and not micrometers. This could be slightly confusing.

-JD


Oh boy… you have no idea how big of a blunder it is for me to get micrometers wrong. :blush: I’ll use the excuse that I was doing most of this Sparki stuff in the evening while sleepy. Let’s not tell anybody beyond this thread, okay? :wink:

Thanks for pointing out the error. It is indeed very confusing, and I will fix it this weekend at the latest.

I’m glad your square is looking pretty good. I’m still trying to find a way to get better accuracy without having to do a lot of manual fiddling around. I had an idea for letting Sparki self-calibrate, and I’ll see how that turns out.

If that’s your biggest blunder, you’re doing pretty well in life!

Self-calibration is a neat idea. What process are you thinking? The accelerometers appear to be low pass filtered already and if Sparki is limited to a flat surface, we might be able to use these as feedback. Navigating with accelerometers is a terrible idea in general, but because it’s essentially a 1D problem…

If external “jigs” are used:

There’s the ultrasonic sensor, I think these things are quoted at 2-3 cm error. Not so bad. Could set up targets x distance apart and use that for calibration.

And probably the easiest: draw a 10cm upper-case ‘I’ and use the IR reflectance sensors.

If I find some time, I’ll try a couple of these out.

-JD

I didn’t mean that this was my biggest blunder, and it’s certainly not the only one. It’s just one more to add to an ever-growing pile. :laughing:

My current approach for self-calibration is essentially what you suggested: Use the center IR sensor to analyze a reference pattern shaped like an uppercase “I”.

Another possible reference for self-calibration could be the magnetic sensor (compass).

Make a 360 turn/spin (in place) and check the orientation. However this also is quite inaccurate…

I’ve considered using the Sparki’s compass/magnetometer for calibration, but I haven’t tried it. Since the magnetometer is almost right above one of the motors, I suspect that it would get a lot of interference while the wheels are moving. As a result, I was concerned that I would not be able to take useful compass measurements while Sparki was turning.

Maybe I’ll play with the magnetometer after I finish my attempt at using the line detection sensors for calibration. One more item on a to-do list that’s already way too long…