Hi, I’m from team 2856 (Sector SAAS) and we’ve been having some really weird problems with our teleop. Our robot has 2 lifters that grab and raise a crate. The lifters are attached to the robot via chains and a motor on each side of the robot. Often times these two lifters run at vastly different speeds despite being set to the same power value. Each motor has encoders attached for bounds, and we are using the built-in RobotC PID for speed control. We have “compensated” for the speed difference by modifying the teleop so that one motor runs at 5% power and the other at 60% power! At the same time that we see this phenomenon, our “turbo” and “slow” parts of our tank drive don’t work (that is all under tankdrive(), see below). The phenomena is buggy and nonpersistent; at times we see it, and at other times the teleop works perfectly with the turbo and slow functioning, and the lifters going at the vastly different speeds which would make sense for powers of 5 and 60. We are using RobotC 3.04, but we also see these bugs on another laptop running RobotC 3.01. I am wondering if this is a version/firmware problem, an issue with DC motor controller, or if it is a problem with our program. We are also using an altered joystickdriver.

//////////////////////////////////////////////////////////////////////////////////////////////////////////// JoystickDriver.h//// A drop-in-compatible replacment for JoystickDriver.c, with enhancements and fixes. Brought// to you by FTC Team 417.//// With the TETRIX system, the PC Controller Station sends messages over Bluetooth or Samantha to the NXT// containing current settings of a PC joystick. The joystick settings arrive using the NXT// "message mailbox" facility.//// End users almost never need to modify this file; rather, one simply #includes it, periodically// calls getJoystickSettings(joystick), and then uses joyBtn(), joyHat(), joyX(), joyY() etc to// examine the joystick state.//////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////// Compability stuff for when this file stands alone//////////////////////////////////////////////////////////////////////////////////////////////////////////

// Names for the directions in which a joystick may be flicked. Note that// that these may be 'or'd together, as in the following example://// if (joyFlickOnce(1,JOY_LEFT,JOYDIR_UP|JOYDIR_DOWN))// {// // Here do stuff common to both up and down// if (joyFlick(1,JOY_LEFT,JOYDIR_UP))// {// // Here do up stuff// }// else// {// // Here do down stuff// }// }//typedef enum { JOYDIR_UP = 1, JOYDIR_DOWN = 2, JOYDIR_LEFT = 4, JOYDIR_RIGHT = 8, } JOYDIR;

// Value for size of the the neutral region of a joystick within which the// joystick is to be considered not to have moved.int joyFlickDeadZone = 45;int joyThrottleDeadZone = 15;

// State of one joystick (there are two of these per controller)typedef struct { short x; // -128 to +127 short y; // -128 to +127 } JOYMSG;

// State of one joystick controller as messaged from the PCtypedef struct { JOYMSG rgjoy[2]; short buttons; // bitmap of values computed from JOYBTN short hat; // values are from JOYHAT } JOYCNTMSG;

// Auxiliary state we keep for each controller beyond that sent from the PCtypedef struct { short buttonsOnce; // Bitmap for managing single button presses short hatOnce; // Bitmap for managing single hat presses short rgflicks[2]; // Bit map for supporting 'flicks' with the two joysticks in this controller } JOYCNTAUX;

// State of all the joystick controllers as updated from messages from the PCtypedef struct { BOOL fTeleOp; // autonomous(false) vs tele-operated(true) mode. BOOL fWaitForStart; // becomes false when the FTC Field Control System permits the program to proceed. long serialNumber; // # of current msg used to set this state. Used to prevent replay of messages. long msReceived; // time on the system clock (nSysTime) at which this message was received. Only updated when serialNumber is incremented. JOYCNTMSG rgcnt[2]; } JOYSTICKSMSG;

// State of all the joystick controllers, both the state from the PC and the once-management statetypedef struct { JOYSTICKSMSG msg; // as updated from the PC. This MUST come first in TJoystick JOYCNTAUX rgaux[2]; // auxiliary state we maintain for each controller beyond that sent from the PC } TJoystick;

//////////////////////////////////////////////////////////////////////////////////////////////////////////// User-callable joystick functions and macros.//// Call getJoystickSettings() to read the latest values from the joystick. It returns true if there's// new data available to process since the last call; false otherwise.//// joyBtn() will return true or false accoring to the current state of the requested button; if the button// is held, true is continually returned. By contrast, joyBtnOnce() will return true immediately after the// button is pressed but will not again return true until the button is released and then pressed again.//// joyHat() and joyHatOnce() behave similarly//////////////////////////////////////////////////////////////////////////////////////////////////////////

// The typical/usual variable used as the parameter for getJoystickSettingsTJoystick joystick;

// Update the joystick state into the indicated variable (almost always 'joystick'). Answer whether// there's any new information about the joystick since the last time getJoystickSettings was called: if// 'false' is returned, you probably should avoid processing the joystick data as you likely did previously// when 'true' was returned.BOOL getJoystickSettings(TJoystick& joystickVar);

// What's a reasonable length of time beyond which we believe that the Field Control System// has just gone away.#ifndef MS_JOYSTICK_FCS_DISCONNECTED_THRESHOLD#define MS_JOYSTICK_FCS_DISCONNECTED_THRESHOLD 1000 // a reasonable value, but not exact by any means#endif

// 'Functions' for accessing the various state of the two joystick controllers. Note that these *assume*// that getJoystickSettings() was called with the variable 'joystick' as its parameter.#define joyBtn(jyc,btn) ((_joyBtnState(jyc) & _joyBtnBit(btn)) != 0)#define joyHat(jyc,hatVal) (joystick.msg.rgcnt[jyc-1].hat==(hatVal))#define joyX(jyc,ijoy) (joystick.msg.rgcnt[jyc-1].rgjoy[ijoy].x)#define joyY(jyc,ijoy) (joystick.msg.rgcnt[jyc-1].rgjoy[ijoy].y)

// Functions that test button and hat state but only return 'true' once per pressBOOL joyBtnOnce(int jyc, int btn);BOOL joyHatOnce(int jyc, int hat);

// 'Flick'ing a joystick allows the four directions on each joystick (on each controller) to be used// very much like additional controller buttons (instead of, say, a throttle as is typically done// when manually driving).//// A 'flick once' will fire only once; it will not refire until the joystick is returned to the neutral// dead zone (this is similar to joyBtnOnce()). If instead you wish to repeatedly fire so long as// the joystick remains displaced, use joyFlick().BOOL joyFlickOnce(int jyc, int ijoy, int flick);BOOL joyFlick(int jyc, int ijoy, int flick);

// When was the last joystick message received?#define joyMessageTime() joystick.msg.msReceived

// How many joystick messages have we seen?#define joyMessageCount() joystick.msg.serialNumber

//////////////////////////////////////////////////////////////////////////////////////////////////////////// Legacy support - compatibility definitions - deprecated; to be eliminated over time.// Try to avoid using these in new code - use joyBtn(), joyX(), etc instead (see above).//////////////////////////////////////////////////////////////////////////////////////////////////////////

// Internal buffer to hold the last received message from the PC (do not directly use)JOYSTICKSMSG _joystickInternal;

// Copy the msg portion of the structure from the internal messaging state.// NB: memcpy, being one opcode, is atomic#define _getJoystickSettingsPrim(joystickVar) memcpy(joystickVar, _joystickInternal, sizeof(_joystickInternal))

// Check to see if a message is available. BOOL fMsgFound = false; while (true) { // There may be more than one message in the queue. We want to get to the last received // message and discard the earlier "stale" messages. This loop simply discards all but // the last message. // int cbMessage = cCmdMessageGetSize(kJoystickQueueID); if (cbMessage <= 0) { if (!fMsgFound) { wait1Msec(4); // Give other tasks a chance to run continue; // No message this time. Loop again } // // No more messages available and at least one message found. We simply discard earlier // messages: each joystick message is self contained, representing the entire state of // of the joysticks, so the earlier ones have no use. // break; }

// cCmdMessageRead returns // ioRsltSuccess on success // ioRsltEmptyMailbox if the message queue is empty (but we call cCmdMessageGetSize first above) // ERR_INVALID_SIZE if the buffer passed here is too small // a few others TFileIOResult nBTCmdRdErrorStatus = cCmdMessageRead((ubyte)rgbT, cbMessage, kJoystickQueueID); if (ioRsltSuccess == nBTCmdRdErrorStatus) { // Repeat loop until there are no more messages in the queue. We only want to process the // last message in the queue. fMsgFound = true; } }

// If control is started with *no* joysticks attached (or at least none logically connected // to RobotC) then the message that arrives from the PC has *entirely* zero values for all joysticks. // Unfortunately, and annoyingly, in that condition, the hat is logically JOYHAT_UP, which // will be interpreted by many programs as the hat being actively pushed, which will cause // some action to be taken by the program, almost certainly in error. As a work around, we // refuse to process any received messages until we see at least one with the first joystick's // hat not seemingly in the 'up' position. So: hands off the hat at the start of your program! if ((_joystickInternal.serialNumber != 0) || (_joystickInternal.rgcnt[0].hat != 0)) { _joystickInternal.serialNumber++; _joystickInternal.msReceived = nSysTime; }

/////////////////////////////////////////////////////////////////////////////////////////////// getUserControlProgram//// This function returns the name of the TETRIX User Control program. It reads the file// "FTCConfig.txt" and builds the name of the file from the contents.//// Note that the filename includes the ".rxe" (robot executable file) file extension./////////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////////////////////// displayDiagnostics//// THis task will display diagnostic information about a TETRIX robot on the NXT LCD.//// If you want to use the LCD for your own debugging use, call the function// "disableDiagnosticsDisplay()/////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////// waitForStart//// Wait for the start of either the autonomous or tele-op phase. User program is running on the NXT// but the phase has not yet started. The FMS (Field Management System) is continually (every 50 msec)// sending information to the NXT. This program loops getting the latest value of joystick settings.// When it finds that the FMS has started the phase, it immediately returns.//// Note: this has been modified to provide an optional escape hatch.///////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef USE_WAIT_FOR_START#define USE_WAIT_FOR_START 1#endif

#if USE_WAIT_FOR_START

// Define here in a simple way so that, at least, this file is self contained.// See also display.h, where a better sol'n is provided.#ifndef DisplayMessage#define DisplayMessage(message) nxtDisplayCenteredTextLine(7, "%s", message)#endif

// With this escape hatch, if the orange button is pressed within the first// two seconds, the waitForStart() terminates. Note that the !(nNxtButtonTask = -1)// at the end of OrangeButtonWaitBreak() is not a bug, but rather is intentionally// both restoring the default button processing after the two seconds and returning// false so as to not terminate the waitForStart().#define OrangeButtonWaitInit() { nNxtButtonTask = -2; /* tell the NXT OS that we want the buttons */ }#define OrangeButtonWaitCond() (MsSinceWaitForStart() <= OrangeButtonWaitTimeThreshold())#define OrangeButtonWaitBreak() (OrangeButtonWaitCond() ? nNxtButtonPressed==kEnterButton: !(nNxtButtonTask = -1))#define OrangeButtonWaitFeedback() { \ if (OrangeButtonWaitDoFeedback()) \ { \ DisplayMessage(OrangeButtonWaitCond() ? "!wait==orange" : "waiting for start"); \ } \ }#define OrangeButtonWaitDone() { \ nNxtButtonTask = -1; /* return buttons to their default processing */ \ DisplayMessage(DISPLAY_MESSAGE_DEFAULT); \ }

Let's analyze one problem at a time. First, let's take a look at the crate lifter. The code is very hard to understand without detail comments about what buttons doing what. But if I understand the code correctly, it looks like you have assigned buttons 6 and 8 for the right crate lifter and buttons 5 and 7 for the left crate lifter. Don't know which buttons are for up and which are for down but it looks like in order to raise the crate, you need to pressed the two left and right "up buttons" simultaneously. When they are pressed, the left side will run with power 18 and the right side with power 20. So you are assuming the right motor is slightly weaker (slower). If I understand the code correctly, you have many problems. First, what if the left and right "up buttons" are not pressed quite at the same time. Then one side will be going up earlier than the other causing the lift to skew. Secondly, what if the there is an operator error where you pressed the up button for one side and the down button for the other side. This could be disasterous. Thirdly, the hard coded power difference between the left and right side will not remain constant. So even if you tune the difference very good at one point will not remain good at other times. I would make the following recommendations:1. Use one button to raise the lifter for both left and right side. Use another button to lower the lifter for both left and right side.2. Use the encoder to synchronize the speed of both sides dynamically and not hard coded power differences. Either doing this synchronization explicitly in your code using PID control or use the RobotC built-in motor synchronization feature. See the RobotC tutorial on this subject. (http://www.robotc.net/teachingmindstorm ... inth8.html).3. If possible, I would design the lifter operating with one motor only, not with two. Our robot also has a lifter and we use one motor to lift both sides with one chain. So there is no worry of synchronizing two motors.

Sat Nov 26, 2011 5:17 am

gcronin

Rookie

Joined: Thu Mar 24, 2011 8:45 pmPosts: 14

Re: Buggy

Hi,

Thanks for the idea. Given the geometry of our robot, it makes sense to run the two lifters separately with separate motors and with the buttons for control. It is not a problem if one goes up and the other goes down; in fact we need this to correct for minor differences in load on both sides of the robot and because the lifters are not professional; they can be "sticky" at times. Therefore even if the same power was applied to the motors, we saw that the motors became unaligned with was really problematic. The independence gives the driver the ability to make decisions and keep both arms as level as possible.

I agree with you that synchronizing the motors would be ideal; the nsynch motors function however does not work with tetrix motors (correct me if i'm wrong... the video you referenced and everything else i have seen which uses this function is for lego motors). we are not quite experienced enough yet to write our own PID to keep the motors completely aligned using the encoders. I also agree that using a single motor would probably be best; again at this stage in the game and given the geometry that is not a possibility.

The bigger problem, however, is the one written about originally; sometimes when we compile the program the two motors run at essentially the same speed. And sometimes the two motors run at drastically different speeds (so drastically different that the powers must be set to 5 and 60 in order to them to be going at the same speed). The loads the motors are seeing are virtually identical. We will try playing with swapping out motor controllers as this could be the issue. But in the way the wiring is setup, the problems are occuring for motors on three different motor controllers so it is difficult to point to one as the problematic controller.

Sat Nov 26, 2011 3:51 pm

MHTS

Guru

Joined: Sun Nov 15, 2009 5:46 amPosts: 1523

Re: Buggy

PID control is not that difficult to write. Assuming you have reset the left lift and right lift encoders when they are perfectly horizontal at the bottom, when you apply power to the two motors, the difference between the two motor powers should be proportional to the difference in their encoder readings. For example:

Basically, if the left and right motors are perfectly synchronized, error will be zero and the motors will get equal power.If the left side goes faster than the right, error will be a positive value. Then the left motor will get a smaller power and the right motor will get a larger power. However, you have to tune the Kp value. You probably can start with Kp = 0.1 and go from there.Note that since I don't know about the polarities of your motors and encoders, you may need to adjust the signs of the code above.With PID contol monitoring the two motor speed, you should not have problems with one running faster than the other at different times.

Sat Nov 26, 2011 11:20 pm

MHTS

Guru

Joined: Sun Nov 15, 2009 5:46 amPosts: 1523

Re: Buggy

gcronin wrote:

At the same time that we see this phenomenon, our “turbo” and “slow” parts of our tank drive don’t work (that is all under tankdrive(), see below). The phenomena is buggy and nonpersistent; at times we see it, and at other times the teleop works perfectly with the turbo and slow functioning, and the lifters going at the vastly different speeds which would make sense for powers of 5 and 60.

Regarding your tank drive problem, like I said in my previous reply, the friction on both sides of the lift may be different at different time. For example, if the lift has been skewed, you probably have a mechanical binding on one or both sides. The side that got stuck will have tremendous resistance causing that side to slow down or stopped while the non-stuck side will keep going. Eventually, if enough skewing happened, both sides will get stuck. While the lift is stuck, your lifter motors will draw tremendous current (stall current) that probably drain down the battery. This probably affects the tank drive motors. That's my guess on what happened. With PID control on the lifter, you may or may not fix this problem. The hope is that if the lifter is running near perfectly horizontal, it is less likely to bind mechanically and so it will run smoothly and not drawing stall current. In addition, you may need to lubricate your lifter tracks to minimize friction and binding.

Sun Nov 27, 2011 4:05 am

gcronin

Rookie

Joined: Thu Mar 24, 2011 8:45 pmPosts: 14

Re: Buggy

So we rewired the robot completely, putting the two drive motors onto a single motor controller, the two crate lifters onto a single motor controller, the servos on one controller and the ball lifter motors on the last motor controller. Here is what we tried:

We put each of the motor controllers on a different port (drive on S1, ball lifter on S2, crates lifters on S3). Incidentally, it wouldn't let us put the servo on S4; didn't allow us to add servos in the motors/sensors setup (it would allow us to edit in the wizards but never updated the pragma config). So on S1 we had drive/servos, S2 ball lifter, S3 crate lifters.

We compiled, ran, worked fine.

Then we tried to daisy chain all the controllers off S1 (drive, ball lifter, crate lifters, servo in that order), ran again, and saw the problem reoccur with both the crate lifters and the turbo not working. So that means we had replicated the problem with motors setup wired to different motor controllers.

Next we swapped out the NXT and put the program on a new NXT with the daisy chained wiring, and the problem persisted... so it's not the NXT.

We moved the motor controllers back to different ports as before (drive on S1, ball lifter on S2, crates lifters on S3) because that configuration seemed to be working. This time, the lifters moved at the same speed, but the turbo/slow settings for the drive motors didn't work. This was the first time we had seen the two problems (lifters/tank drive) isolated from each other. So then we decided to see if we could get the problem to move with the port; we switched S1 and S3 and the program worked.

I'm really having a hard time... i can't see any pattern emerging...

Given that at one point we saw the problem occurring only with the tank drive motors, we could try swapping out that motor controller... that's about the only thing i can think of at this point.

Wed Nov 30, 2011 10:26 pm

ronmcrae

Rookie

Joined: Wed Jul 21, 2010 11:23 pmPosts: 39

Re: Buggy

gcronin wrote:

I'm really having a hard time... i can't see any pattern emerging...

Given that at one point we saw the problem occurring only with the tank drive motors, we could try swapping out that motor controller... that's about the only thing i can think of at this point.

This may be totally off-base, but have you tried replacing the NXT cables? I read that the daisychain addressing of the HiTechnic controllers is achieved in some fashion using the analog line on the port. If you have a bad cable I assume that could play havoc with the addressing and create some very peculiar behavior.

Who is online

Users browsing this forum: No registered users and 2 guests

You cannot post new topics in this forumYou cannot reply to topics in this forumYou cannot edit your posts in this forumYou cannot delete your posts in this forumYou cannot post attachments in this forum