![]() |
Re: Gyro Repeatability, Expected Drift ?
Quote:
for the San Jose Regional keeps you busy. We are using the SparkFun mounting of the ADXRS150. We also have one of the IC style mountings from digikey on our test mule, but we bought the SparkFun rendition this year to avoid etching a PC board during a busy build period. All you have to hook up on the SparkFun unit is 5v, signal, and gnd. The other outputs support advanced compensation methods. The biggest effect on the bias is the temperature. The temperature of the autoshop where we build and test the robot is different than that at a regional, so one has to recalibrate the bias if one is not using temperature compensation. There is also power supply voltage compensation, but the 5 volt supply should be pretty well regulated unless there are large loads placed on it. I have not had any time to package any code up, but here are some nippets on how we do things. Note the conditional compilation for the timer interrupt handler. One calculates the integral, the other calculates the sum of 256 samples. To avoid doing many sums and averaging, you can do 2560 and then divide by 10, rounding. /* Initialize_Timer_1() sets up the timer used to control the time integration for the rotational rate gyro. Called from User_Initialization() in user_routines.c. Refer to Kevin Watson's "frc_interrupts" code for the details and additional examples of other timers. Kevin also has cached copies of the PIC Microcontroller documentation on his web site at http://kevin.org/frc/. If you contemplate any adjustments you will need to study this documentation carefully. The only changes we recommend to this routine are to select different prescaler values in order to slow down the interrupt rate if desired. Out of respect for Nyquist, you should not drop the interrupt rate below twice the bandwidth of the gyro. */ void Initialize_Timer_1(void) { /* Initialize the 16 bit timer register. */ TMR1L = 0x00; TMR1H = 0x00; /* Set the prescaler for a 10 MHz rate. With the interrupt happening on the overflow of a 16 bit counter, the interrupt rate is 10MHz / 2^16, or 152.59 Hz. The timer interrupt rate can be reduced by a factor of 2, 4, or 8 by picking one of the other prescaler values. */ T1CONbits.T1CKPS0 = 0; // T1CSP1 T1CSP0 T1CONbits.T1CKPS1 = 0; // 0 0 1:1 prescaler (clock=10MHz/each tick=100ns) // 0 1 1:2 prescaler (clock=5MHz/each tick=200ns) // 1 0 1:4 prescaler (clock=2.5MHz/each tick=400ns) // 1 1 1:8 prescaler (clock=1.25MHz/each tick=800ns) // T1CONbits.T1OSCEN = 0; /* Leave the timer 1 internal oscillator disaled. */ T1CONbits.TMR1CS = 0; /* Use the internal clock. */ T1CONbits.RD16 = 1; /* Do timer 1 register operations in one 16 bit access. */ IPR1bits.TMR1IP = 0; /* Set interrupt priority to low. */ PIR1bits.TMR1IF = 0; /* Clear any existing interrupt. */ PIE1bits.TMR1IE = 1; /* Enable interrupt on overflow, a transition of the counter from FFFF -> 0. */ T1CONbits.TMR1ON = 1; /* Enable the timer. */ } static volatile signed long GyroIntegral = 0; // in 256ths static volatile unsigned char GyroIntegralSentinel = 0; static volatile unsigned long OldGyroValue = 0; static volatile int AverageCycleCount = 0; /* The value for GyroAverage is obtained by compiling the code to sample and produce the average upon the trigger pull. You sample the average a number of times and average the results to produce the value that is hard coded here. */ static volatile unsigned long GyroAverage = 131419; // in 256ths /* Timer_1_Int_Handler() handles that interrupt based calculation of the average of the gyro signal. A sentinel is used to inform the interrupt handler that the gyro integral is being zeroed by user code. */ #define GYROINPUT rc_ana_in01 #ifdef GYROAVERAGE void Timer_1_Int_Handler(void) { if(GyroIntegralSentinel == 0) { if(AverageCycleCount < 256) { GyroIntegral += Get_Analog_Value(GYROINPUT); AverageCycleCount += 1; } } } #else void Timer_1_Int_Handler(void) { if(GyroIntegralSentinel == 0) { unsigned long NewGyroValue = Get_Analog_Value(GYROINPUT); GyroIntegral += ((OldGyroValue + NewGyroValue) << 7) - GyroAverage; OldGyroValue = NewGyroValue; } else { OldGyroValue = Get_Analog_Value(GYROINPUT); } } #endif /* GetGyroIntegral() returns the current value of the time integral of the rotational rate gyro. As interrupts are active, the value read is reliable if two successive reads produce the same value. */ signed long GetGyroIntegral(void) { signed long Old, New; Old = GyroIntegral; while(Old != (New = GyroIntegral)) { Old = New; } #ifdef GYROAVERAGE return Old; #else return Old / 10000L; #endif } /* ZeroGyroIntegral() zeros the average of the rotational rate gyro signal that is calculated via the timer interrupt. A sentinel is used to inform the interrupt handler that the values are in the process of being zeroed by user code. */ void ZeroGyroIntegral(void) { GyroIntegralSentinel = 1; GyroIntegral = 0; AverageCycleCount = 0; GyroIntegralSentinel = 0; } The interrupt handler setup is what you would expect: #pragma interruptlow InterruptHandlerLow save=PROD,section("MATH_DATA"),section(".tmpdata") void InterruptHandlerLow () { unsigned char int_byte; if (PIR1bits.TMR1IF && PIE1bits.TMR1IE) { /* Timer 1 */ PIR1bits.TMR1IF = 0; Timer_1_Int_Handler(); } else if (INTCON3bits.INT2IF && INTCON3bits.INT2IE) { /* The INT2 pin is RB2/DIG I/O 1. */ INTCON3bits.INT2IF = 0; Int_1_Handler(); } The setup of the timer gets put in User_Initialization Initialize_Timer_1(); Putdata(&txdata); /* DO NOT CHANGE! */ And, finally, some code in the main packet loop allows you to print the value of sum, or integral, and reset it First, a useful function for printing longs #define RLEN 10 /* Maximum decimal digits for an unsigned long. */ /* The function puln(unsigned long i, unsigned char n) prints "i" in decimal format, using leading zeros to fill out a minimum field width of "n" characters. */ void puln(unsigned long i, unsigned char n) { unsigned char result[RLEN]; /* Stores the result digits, as numbers. */ unsigned char reversed[RLEN]; /* Storage for the digits as they come off. */ char rpos; /* Index for reversed array. */ char rindex; /* Index for result array. */ int j; /* The minimum field width is one. */ if(n == 0) { n = 1; } /* Use remainder and divide in a loop to peel off the digits, least significant first. */ rpos = 0; while(i > 0) { reversed[rpos] = i % 10; rpos += 1; i /= 10; } /* At this point the digits are in the reversed array and rpos records the count. If rpos is zero, the argument was zero. We must first place the required number of leading zeros in the result. */ rindex = 0; while((rindex + rpos) < n) { result[rindex] = 0; rindex += 1; } /* Fill in the digits, reversing the order. */ while(rpos > 0) { result[rindex] = reversed[rpos-1]; rindex += 1; rpos -= 1; } /* You gotta just love the fact that printf() on the RC can't handle a character string in data memory! Don't forget the cast to (int) for the RC... */ for(j = 0; j < rindex; j += 1) { printf("%d", (int)result[j]); } } /* Print a signed long. */ void pl(long i) { if(i < 0) { printf("-"); i = -i; } puln(i, 1); printf("\r"); } /* Print an unsigned long. */ void pul(unsigned long i) { puln(i, 1); printf("\r"); } /* The function void pfp(long i, unsigned char bits, unsigned char digits) prints a binary fixed point value in decimal format. The arguments are: i, the integer holding the fixed point value; bits, the number of bits to place after the binary pont; and digits, the number of digits after the decimal point to print. */ void pfp(long i, unsigned char bits, unsigned char digits) { long intpart; long mask; long powten; unsigned char j; /* If the argument is negative, print the sign and work with the absolute value so that shifts give us a divide. */ if(i < 0) { printf("-"); i = -i; } /* Shift the argument down to obtain the integer part, and turn the result over to puln() to handle. */ intpart = i >> bits; puln(intpart,1); printf("."); /* Construct the mask for the fractional part. */ mask = 0; /* All zeros. */ mask = ~mask; /* All ones. */ mask <<= bits; /* Shift in bits zeros. */ mask = ~mask; /* Flip to get bits ones. */ /* Use the mask to obtain the fractional part. */ i &= mask; /* Convert the fraction to a decimal fraction. */ powten = 1; for(j = 1; j <= digits; j += 1) { i *= 10; powten *= 10; } i >>= bits; /* At this point, we print the decimal fraction with the correct number of digits using leading zeros. */ puln(i, digits); /* And finally, the optional newline. */ printf("\r"); } And then using it to print the sum, or integral #ifdef NOTDEF if(p3_sw_trig == 1) { printf("trig pressed\r"); ZeroGyroIntegral(); } if(counter % 40 == 0) { printf("Gyro Sum = "); pl(GetGyroIntegral()); } #endif I never define NOTDEF by convention, and if I want the code in I change ifdef to ifndef In this case, if the named trigger is pressed, the gyrointegral, or the sum is zeroed. In the case of the integral, you can then watch the drift. It is an integer in roughly tenths of a degree. In the case of the sum, it steadily increases until the count in the interrupt handler is reached, and then you hard code the number into the integrator. We usually use the average of 10 or 20 samples, and compute the average with a calculator. This can be more automatically done by changing 256 to 2560 in the timer interrupt handler and then dividing the result by 10. You could take it to its limit by using 25600 samples and dividing by 100, rounding the result. It is all pretty straight forward. Check the drift perhaps a couple of times a day, and if the drift gets to be too much for you, redo the calibration. A "big" drift is perhaps one degree, or at most two, during the 15 second autonomous period. By recalibration you can reduce the drift to a tenth of a degree or so. I have had time to look for the Amtel oversampling documentation and look at it this evening. The goal of that approach is to improve the resolution of the direct gyro signal by oversampling, using the noise to produce a higher resolution average. In essense, we are playing the same game with the integration approach laid out above and also suggested many times by Chris Hibner. One need not oversample so much to exploit the technique when what you want is the integral, and in fact there is a point of diminishing returns due to the increased correlation between successive points. If what I wanted was the best possible direct rotation rate signal, I would use the Amtel approach that Kevin has coded, but since what I want is the integral, the strategy shown above is, in my view, the best one and it does not require one to burden the PIC processor with a high interrupt rate. Gotta get some sleep, it will be a long day tomorrow. Eugene |
Re: Gyro Repeatability, Expected Drift ?
Thanks for the outstanding heads up on this stuff.
I waited one day and now both Sparkfun and Digikey went out of stock on the ADXRS300 stuff. But Digikey is due back soon. Our Robot (in quarantine) currently has the 2008 gyro. A test robot I believe has the 2007 and I am testing at home with which is now clear to me to be the 2006 gyro. It sounds like the 2007 and 2006 have twice the max rate of the 2008 so maybe we should use them on the robot. However, it is the 2006 that is having the repeatabilitity issues on a compass board. |
| All times are GMT -5. The time now is 13:28. |
Powered by vBulletin® Version 3.6.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright © Chief Delphi