Chief Delphi

Chief Delphi (http://www.chiefdelphi.com/forums/index.php)
-   Programming (http://www.chiefdelphi.com/forums/forumdisplay.php?f=51)
-   -   Programming tricks (and former trade secrets) (http://www.chiefdelphi.com/forums/showthread.php?t=54668)

The Lucas 24-02-2007 17:08

Re: Programming tricks (and former trade secrets)
 
Quote:

Originally Posted by Cuog (Post 585663)
You can use the LEDs on the OI and have them turn on when the joystick input is where you want it.(for info on this look at the bottom of default_routine() in the default code

Quote:

Originally Posted by Jake M (Post 585701)
Very true, but my whole reasoning for making such a circuit is not simply to let us see when the joysticks are trimmed or aren't, it's to let us do so without using up other OI features (such as the LEDs) which we could use for other feedback, concerning the actual driving of the robot. We can only acesss 8 of them, through the joystick ports and we could easily find 8 other uses for an LED.

I wrote a routine like this years ago and it goes into the code for both my teams every year. Here is what I use: ( I just added some defines for portability)

Code:

#define CALAXIS1        p1_y
#define CALAXIS2        p1_x
#define CALAXIS3        p3_y
#define CALAXIS4        p3_x


if (disabled_mode)
{
        //when joysticks are calibrated
        //all 8 LEDs will be on
        Pwm1_green = (CALAXIS1 >= 127);
        Pwm1_red = (CALAXIS1 <= 127);
        Pwm2_green = (CALAXIS2 >= 127);
        Pwm2_red = (CALAXIS2 <= 127);
        Relay1_green = (CALAXIS3 >= 127);
        Relay1_red = (CALAXIS3 <= 127);
        Relay2_green = (CALAXIS4 >= 127);
        Relay2_red = (CALAXIS4 <= 127);
}
else
{
        //whatever you normally do with the LEDs
}

It only uses the LEDs while the bot is disabled, since it should be calibrated before enabling. You are free to use the LEDs as you would during the match, just put that code in the else statement. Just make sure your LEDs are set every loop to avoid confusing the drivers with stray lights.

Basically you just have to play with the calibration trimmers until both red and green LEDs are ON for that axis. Might be tough at first but you will get the touch. All 8 LEDs ON = "Calibrated and ready to go". :)

JimGRobot 24-02-2007 21:23

Re: Programming tricks (and former trade secrets)
 
Thanks for the note, this is a very good point...

We see the same behavior whenever we reset the RC.

Fortunately, our offsets are small, so the bot doesn't go nuts.

Thanks,
Jim

Dave K. 26-02-2007 01:34

Re: Programming tricks (and former trade secrets)
 
Quote:

Originally Posted by Alan Anderson (Post 582591)
Anyone else feel like sharing?

Streamlined, non-directional Encoder Tach code and added a ring buffer. This is used with 100 counts per revolution encoders on each wheel and provides good low speed resolution while still allowing the PID loop to compute new values every 26ms.

Some will recognize Kevin's interrupt framework, but everything else is different.

Here's the relevant portion of the interrupt routine:

Code:


volatile unsigned int Tach_Count[NUM_TACH];
unsigned char Old_Port_B = 0xFF;

void InterruptHandlerLow () {

        unsigned char Port_B;
        unsigned char Port_B_Delta;

  // other interrupt handler code removed for clarity.

        else if (INTCON3bits.INT2IF && INTCON3bits.INT2IE) { // encoder 1 interrupt?
                INTCON3bits.INT2IF = 0; // clear the interrupt flag
                Tach_Count[0]++;
        }
        else if (INTCON3bits.INT3IF && INTCON3bits.INT3IE) { // encoder 2 interrupt?
                INTCON3bits.INT3IF = 0; // clear the interrupt flag
                Tach_Count[1]++;
        }
        else if (INTCONbits.RBIF && INTCONbits.RBIE) { // encoder 3-6 interrupt?
                Port_B = PORTB; // remove the "mismatch condition" by reading port b           
                INTCONbits.RBIF = 0; // clear the interrupt flag
                Port_B_Delta = Port_B ^ Old_Port_B; // determine which bits have changed
                Old_Port_B = Port_B; // save a copy of port b for next time around
       
                if(Port_B_Delta & 0x10) { // did external interrupt 3 change state?
                        if (Port_B & 0x10) {
                                Tach_Count[2]++;
                        }
                }
                if(Port_B_Delta & 0x20) { // did external interrupt 4 change state?
                        if (Port_B & 0x20) {
                                Tach_Count[3]++;
                        }
                }
        }
}


Initialization:

Code:

unsigned char i, j;

        for(i=0;i<NUM_TACH;i++) {
                TachHead[i] = 0;
                for(j=0;j<TACHBUFSIZE;j++) {
                        TachBuf[i][j] = 0;
                }       
        }

And here's the foreground tach code that runs in the 26ms loop:

Code:

#define NUM_TACH 4
#define TACHBUFSIZE 4

extern unsigned int Tach_Count[];  // interrupt routine edge counter
unsigned char i, j;
unsigned int TachBuf[NUM_TACH][TACHBUFSIZE];  // ring buffer
unsigned char TachHead[NUM_TACH];  // TachBuf head pointer
unsigned int Tach[NUM_TACH];  // Tach result register.


                        // disable tach interrupts, grab current count, and reset counter

                        INTCON3bits.INT2IE = 0;                // Disable INT2 for tach 1
                        INTCON3bits.INT3IE = 0;                // Disable INT3 for tach 2
                        INTCONbits.RBIE = 0;                // Disable Port B change interrupt for tach 3&4

                        // get current tach values into local var, then reset the counter
                        for (i=0;i<NUM_TACH;i++) {
                                TachBuf[i][TachHead[i]] = Tach_Count[i];        // place current tach count into ring buffer & inc buffer
                                Tach_Count[i] = 0;
                        }

                        INTCON3bits.INT2IE = 1;                // INT2 on
                        INTCON3bits.INT3IE = 1;                // INT3 on
                        INTCONbits.RBIE = 1;                // Port B change interrupt on


                        // sum up the tach counts in ring buffer into Tach[]
                        for (i=0;i<NUM_TACH;i++) {
                                Tach[i] = 0;
                                for(j=0;j<TACHBUFSIZE;j++) {
                                        Tach[i] += TachBuf[i][j];
                                }
                                if (++TachHead[i] >= TACHBUFSIZE) {
                                        TachHead[i] = 0;
                                }
                        }

                        // Tach[] now contains the unsigned wheel speeds


Alan Anderson 26-02-2007 06:55

Re: Programming tricks (and former trade secrets)
 
Quote:

Originally Posted by Dave K. (Post 586460)
Streamlined, non-directional Encoder Tach code and added a ring buffer. This is used with 100 counts per revolution encoders on each wheel and provides good low speed resolution while still allowing the PID loop to compute new values every 26ms.

We did that for the shooter wheel last year, but with a twist. Recognizing that the buffer is essentially a filter that slows down the measurement response, we added the newest value from the buffer multiple times. The resolution stays high, but the sum more quickly represents a change in velocity.

I also came up with an interesting result when I figured how long it took to maintain the ring buffer. With as many as ten entries, it turned out to be faster to move all of the values every time than to index through an array. Array operations are convenient and make for easier coding in many cases, but they can be remarkably slow.

Joohoo 26-02-2007 08:24

Re: Programming tricks (and former trade secrets)
 
we worked on writing a new scripting language for autonomous mode.

so easy to write new logic I had mechanical people writing some stuff.

Dave K. 26-02-2007 13:37

Re: Programming tricks (and former trade secrets)
 
Quote:

Originally Posted by Alan Anderson (Post 586484)
We did that for the shooter wheel last year, but with a twist. Recognizing that the buffer is essentially a filter that slows down the measurement response, we added the newest value from the buffer multiple times. The resolution stays high, but the sum more quickly represents a change in velocity.

I also came up with an interesting result when I figured how long it took to maintain the ring buffer. With as many as ten entries, it turned out to be faster to move all of the values every time than to index through an array. Array operations are convenient and make for easier coding in many cases, but they can be remarkably slow.

I agree that a weighted average approach can help. In this case, to get the resolution we needed, a sample window of about 4*26 (100ms) worked out about right, but only performing the PID calculations at that rate, made the loop somewhat hard to optimize and user response more sluggish.

I don't doubt that individual moves would be faster as it avoids one or more multiply operations to compute the array index.

Although the interrupt routines use an arrayed variable to store the tach counts, the use of an absolute index allows the compiler and linker to resolve this to a fixed memory location at build time.

The overall simplification of Kevin's code by reworking the logic to remove direction sensing as well as the overhead of function calls helps reduce the CPU loading where the impact is the greatest.

My primary point in sharing the idea was to provide a simpler example of how a velocity only interrupt could work, and to introduce the idea of using a sliding window for data values to improve resolution.


I chose to use arrayed variables in the 26ms loop for the tach code as well as all of the remaining PID code just to help with readability and to make it easy to change the size of the ring buffer, or add/delete motors.

Certainly if the CPU didn't have any idle time left, the arrays are one of the first things I'd go after.

Jake M 26-02-2007 23:43

Re: Programming tricks (and former trade secrets)
 
[quote=The Lucas;585702]It only uses the LEDs while the bot is disabled, since it should be calibrated before enabling. You are free to use the LEDs as you would during the match, just put that code in the else statement. Just make sure your LEDs are set every loop to avoid confusing the drivers with stray lights.[quote]

Now, why didn't I think of that? Thanks for the tip.

PhilBot 27-02-2007 10:32

Re: Programming tricks (and former trade secrets)
 
Quote:

Originally Posted by WesleyC (Post 583888)
The code goes something like this:

output = (input * input / 127) * (input > 0) ? 1 : -1;

This is a great piece of math, but when I tried it, it didn't work...
It only served to reinforce my motto "when in doubt, add parenthesis".

It appears that the compiler gives greater precedence to * than it does to ?

So what the code was doing was this:

output = ((input * input / 127) * (input > 0)) ? 1 : -1;

not this:

output = (input * input / 127) * ((input > 0) ? 1 : -1);


As written, the code only returned +/- 1. With parenthesis added as in the second example, the code did what was intended.

The other cool by-product of this code is that it adds in a small dead-band to the joystick. Any offset less than 13 returns a zero after interger math truncation.

Dave K. 27-02-2007 14:02

Re: Programming tricks (and former trade secrets)
 
Quote:

Originally Posted by PhilBot (Post 587085)
This is a great piece of math, but when I tried it, it didn't work...
It only served to reinforce my motto "when in doubt, add parenthesis".

It appears that the compiler gives greater precedence to * than it does to ?

So what the code was doing was this:

output = ((input * input / 127) * (input > 0)) ? 1 : -1;

not this:

output = (input * input / 127) * ((input > 0) ? 1 : -1);

Operator precedence for Multiplicative and Additive instructions is relatively high, whereas the ternary operator is quite low, however the '?' forms a 'sequence point' and considers anything to the left, within the sequence, to be used as the scalar value.

Parenthesis is, of course, the correct way to create the desired association, and when using the ternary operator is a good habit to adopt.

WesleyC 01-03-2007 11:25

Re: Programming tricks (and former trade secrets)
 
Thanks for the notes!

I'm coming from a PHP background--all math is given equal precedence in PHP; you use parenthesis to emphasize certain points to be performed first--also, all expressions are evaluated before math is performed.

Also, one thing you must be careful of when using this particular phrase is that the input variable must either be an int or typecast to an int before performing math. If "input" is a char, the math will overflow the size of the type, creating... shall we say, "undesirable" results. :P

Dave K. 01-03-2007 15:22

Re: Programming tricks (and former trade secrets)
 
Quote:

Originally Posted by WesleyC (Post 588212)
Thanks for the notes!

I'm coming from a PHP background--all math is given equal precedence in PHP; you use parenthesis to emphasize certain points to be performed first--also, all expressions are evaluated before math is performed.

Also, one thing you must be careful of when using this particular phrase is that the input variable must either be an int or typecast to an int before performing math. If "input" is a char, the math will overflow the size of the type, creating... shall we say, "undesirable" results. :P

Without disagreeing with the comment about type casting...

If the compiler is really standards compliant, objects or expressions of rank less than int (such as unsigned char) are converted to int (if int can represent all values of the original type) or unsigned int (otherwise); this is known as integer promotion.

In the case of multiplying two unsigned chars, the the product of two values of unsigned type will be reduced according to the range of values for that type.

This is an area where compiler's behavior will sometimes vary, especially those targeted towards 8 bit native platforms (such as the PIC18) where math with 8 bit variables is not always evaluated as an integer (which in this case is 16 bits).

Not all compilers correctly perform integer promotion, and I've only run across one that did not behave any better when type casts were applied.

In the case of the PIC18 compiler it does understand integer promotions, however the default behavior is to not do it. The C compiler documentation covers this in section 2.7 (ISO DIVERGENCES).

Snappel328 02-03-2007 23:40

Re: Programming tricks (and former trade secrets)
 
Well, we've got some good things going on. I guess everyone else was either hurrying or something with their programming, because we managed to play quite a lot of Counter-Strike and still have a perfectly functional robot heh.

Qbranch 03-03-2007 12:36

Re: Programming tricks (and former trade secrets)
 
*to the tune of California Dreamin'*

CCPs make me happy... when the skys are grey... when i have the pwm blues... on a winter's day... update rate ga-lore... when the skys are grey-hey.... :cool:

keep on dreamin... and good luck in Rack N' Roll!

(by the way, low freq SDAR, team 1024's pet project, autonomous mode? we'll find out... *hint hint*)

-q

JohnC 09-03-2007 01:07

Re: Programming tricks (and former trade secrets)
 
Our drive code was such that 3/54 teams approached us specifically asking how we get such fine control of our robot.

I know this is a lot of code, and it's not very advanced, but man does it work well.

Code:

int driveScaleFactor = 3;

void Left(int speed) {
  pwm03 = Safe_Values(speed);
}

void Right(int speed) {
  pwm04 = Safe_Values(254-speed);
}

int Safe_Values(int roughValue) {
  int toReturn = roughValue;

  if(toReturn> 254) {
    toReturn = 254;
  } else if(toReturn < 1) {
    toReturn = 1;
  }

  return toReturn;
}

int Scale_Offset(int roughValue, int scaleFactor) {
  int toReturn = roughValue;

  toReturn = ((toReturn-127)/scaleFactor) + 127;

  return toReturn;
}

//// later, in Process_Data_From_Master_uP()

if(p1_sw_trig && p2_sw_trig) {
  Left(p1_y);
  Right(p2_y);
} else {
  Left(Scale_Offset(p1_y,driveScaleFactor));
  Right(Scale_Offset(p2_y,driveScaleFactor));
}

The advantage of having a function to assign pwm values instead of assigning them inline, is, obviously, you can add other functions like the one I did. Let me know if you use this!

I will eventually post code that uses PD control with encoders to drive in a straight line autonomously.

AustinSchuh 09-03-2007 01:38

Re: Programming tricks (and former trade secrets)
 
We wrote our own sin() function.
It takes a degree measure in in "funnys", where there are 40 "funnys" per circle. We then directly map that angle to the table after correcting the sign of the number. We are using this to implement a feed forward controller with our PID controller. We will be adding in a function on Thursday that will take 2 angles and some other various data in from our double jointed arm, and output the torque that is being experienced on the "shoulder" joint and implement that into the PID controller for the shoulder.

I also wrote our drive code so that the steering wheel we use specifies the difference in motor power between the left and the right wheels. This even works when the throttle is full forward. The code calculates it out so that even though it would like to run one motor at over full power, it lowers the other motor's power further instead to retain this difference in power.

I like the reduced sensitivity code that I saw earlier. I think that I am going to implement a version of that using one of the buttons on our steering wheel to turn the mode on or off.

Our sin(x) function:
Code:

static const int8 sin_table[] = {0, 20, 39, 58, 75, 90, 103, 113, 121, 125, 127};
int sin(int x) {
        char negate = 0;
        int result;

        // sin(-x) = -sin(x)
        if (x < 0) {
                x = -x;
                negate = 1;
        }

        // sin(x) = sin(x - 360 degrees)
        while (x >= 40) x -= 40;

        if (x > 30) {
                x = 40 - x;
                negate = !negate;
        } else if (x > 20) {
                x = x - 20;
                negate = !negate;
        } else if (x > 10) {
                x = 20 - x;
        }
        result = (int) sin_table[x];
        if (negate) result = -result;
        return result;
}



All times are GMT -5. The time now is 18:06.

Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright © Chief Delphi