001    /*----------------------------------------------------------------------------*/
002    /* Copyright (c) FIRST 2008-2012. All Rights Reserved.                        */
003    /* Open Source Software - may be modified and shared by FRC teams. The code   */
004    /* must be accompanied by the FIRST BSD license file in the root directory of */
005    /* the project.                                                               */
006    /*----------------------------------------------------------------------------*/
007    package edu.wpi.first.wpilibj;
008    
009    import edu.wpi.first.wpilibj.communication.UsageReporting;
010    import edu.wpi.first.wpilibj.livewindow.LiveWindow;
011    import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable;
012    import edu.wpi.first.wpilibj.parsing.ISensor;
013    import edu.wpi.first.wpilibj.tables.ITable;
014    import edu.wpi.first.wpilibj.util.BoundaryException;
015    
016    /**
017     * Use a rate gyro to return the robots heading relative to a starting position.
018     * The Gyro class tracks the robots heading based on the starting position. As the robot
019     * rotates the new heading is computed by integrating the rate of rotation returned
020     * by the sensor. When the class is instantiated, it does a short calibration routine
021     * where it samples the gyro while at rest to determine the default offset. This is
022     * subtracted from each sample to determine the heading.
023     */
024    public class Gyro extends SensorBase implements PIDSource, ISensor, LiveWindowSendable {
025    
026        static final int kOversampleBits = 10;
027        static final int kAverageBits = 0;
028        static final double kSamplesPerSecond = 50.0;
029        static final double kCalibrationSampleTime = 5.0;
030        static final double kDefaultVoltsPerDegreePerSecond = 0.007;
031        AnalogChannel m_analog;
032        double m_voltsPerDegreePerSecond;
033        double m_offset;
034        int m_center;
035        boolean m_channelAllocated;
036        AccumulatorResult result;
037        private PIDSourceParameter m_pidSource;
038    
039        /**
040         * Initialize the gyro.
041         * Calibrate the gyro by running for a number of samples and computing the center value for this
042         * part. Then use the center value as the Accumulator center value for subsequent measurements.
043         * It's important to make sure that the robot is not moving while the centering calculations are
044         * in progress, this is typically done when the robot is first turned on while it's sitting at
045         * rest before the competition starts.
046         */
047        private void initGyro() {
048            result = new AccumulatorResult();
049            if (m_analog == null) {
050                System.out.println("Null m_analog");
051            }
052            m_voltsPerDegreePerSecond = kDefaultVoltsPerDegreePerSecond;
053            m_analog.setAverageBits(kAverageBits);
054            m_analog.setOversampleBits(kOversampleBits);
055            double sampleRate = kSamplesPerSecond * (1 << (kAverageBits + kOversampleBits));
056            m_analog.getModule().setSampleRate(sampleRate);
057    
058            Timer.delay(1.0);
059            m_analog.initAccumulator();
060    
061            Timer.delay(kCalibrationSampleTime);
062    
063            m_analog.getAccumulatorOutput(result);
064    
065            m_center = (int) ((double)result.value / (double)result.count + .5);
066    
067            m_offset = ((double)result.value / (double)result.count) - (double)m_center;
068    
069            m_analog.setAccumulatorCenter(m_center);
070    
071            m_analog.setAccumulatorDeadband(0); ///< TODO: compute / parameterize this
072            m_analog.resetAccumulator();
073            
074            setPIDSourceParameter(PIDSourceParameter.kAngle);
075    
076            UsageReporting.report(UsageReporting.kResourceType_Gyro, m_analog.getChannel(), m_analog.getModuleNumber()-1);
077            LiveWindow.addSensor("Gyro", m_analog.getModuleNumber(), m_analog.getChannel(), this);
078        }
079    
080        /**
081         * Gyro constructor given a slot and a channel.
082        .
083         * @param slot The cRIO slot for the analog module the gyro is connected to.
084         * @param channel The analog channel the gyro is connected to.
085         */
086        public Gyro(int slot, int channel) {
087            m_analog = new AnalogChannel(slot, channel);
088            m_channelAllocated = true;
089            initGyro();
090        }
091    
092        /**
093         * Gyro constructor with only a channel.
094         *
095         * Use the default analog module slot.
096         *
097         * @param channel The analog channel the gyro is connected to.
098         */
099        public Gyro(int channel) {
100            m_analog = new AnalogChannel(channel);
101            m_channelAllocated = true;
102            initGyro();
103        }
104    
105        /**
106         * Gyro constructor with a precreated analog channel object.
107         * Use this constructor when the analog channel needs to be shared. There
108         * is no reference counting when an AnalogChannel is passed to the gyro.
109         * @param channel The AnalogChannel object that the gyro is connected to.
110         */
111        public Gyro(AnalogChannel channel) {
112            m_analog = channel;
113            if (m_analog == null) {
114                System.err.println("Analog channel supplied to Gyro constructor is null");
115            } else {
116                m_channelAllocated = false;
117                initGyro();
118            }
119        }
120    
121        /**
122         * Reset the gyro.
123         * Resets the gyro to a heading of zero. This can be used if there is significant
124         * drift in the gyro and it needs to be recalibrated after it has been running.
125         */
126        public void reset() {
127            if (m_analog != null) {
128                m_analog.resetAccumulator();
129            }
130        }
131    
132        /**
133         * Delete (free) the accumulator and the analog components used for the gyro.
134         */
135        public void free() {
136            if (m_analog != null && m_channelAllocated) {
137                m_analog.free();
138            }
139            m_analog = null;
140        }
141    
142        /**
143         * Return the actual angle in degrees that the robot is currently facing.
144         *
145         * The angle is based on the current accumulator value corrected by the oversampling rate, the
146         * gyro type and the A/D calibration values.
147         * The angle is continuous, that is can go beyond 360 degrees. This make algorithms that wouldn't
148         * want to see a discontinuity in the gyro output as it sweeps past 0 on the second time around.
149         *
150         * @return the current heading of the robot in degrees. This heading is based on integration
151         * of the returned rate from the gyro.
152         */
153        public double getAngle() {
154            if (m_analog == null) {
155                return 0.0;
156            } else {
157                m_analog.getAccumulatorOutput(result);
158    
159                long value = result.value - (long) (result.count * m_offset);
160    
161                double scaledValue = value * 1e-9 * m_analog.getLSBWeight() * (1 << m_analog.getAverageBits()) /
162                        (m_analog.getModule().getSampleRate() * m_voltsPerDegreePerSecond);
163    
164                return scaledValue;
165            }
166        }
167        
168        /**
169         * Return the rate of rotation of the gyro
170         * 
171         * The rate is based on the most recent reading of the gyro analog value
172         * 
173         * @return the current rate in degrees per second
174         */
175        public double getRate() {      
176            if(m_analog == null) {
177                return 0.0;
178            } else {
179                return (m_analog.getAverageValue() - ((double)m_center + m_offset)) * 1e-9 * m_analog.getLSBWeight() 
180                        / ((1 << m_analog.getOversampleBits()) * m_voltsPerDegreePerSecond);
181            }
182        }
183    
184        /**
185         * Set the gyro type based on the sensitivity.
186         * This takes the number of volts/degree/second sensitivity of the gyro and uses it in subsequent
187         * calculations to allow the code to work with multiple gyros.
188         *
189         * @param voltsPerDegreePerSecond The type of gyro specified as the voltage that represents one degree/second.
190         */
191        public void setSensitivity(double voltsPerDegreePerSecond) {
192            m_voltsPerDegreePerSecond = voltsPerDegreePerSecond;
193        }
194        
195        /**
196         * Set which parameter of the encoder you are using as a process control variable.
197         * The Gyro class supports the rate and angle parameters
198         * @param pidSource An enum to select the parameter.
199         */
200        public void setPIDSourceParameter(PIDSourceParameter pidSource) {
201            BoundaryException.assertWithinBounds(pidSource.value, 1, 2);
202            m_pidSource = pidSource;
203        }
204    
205        /**
206         * Get the angle of the gyro for use with PIDControllers
207         * @return the current angle according to the gyro
208         */
209        public double pidGet() {
210            switch (m_pidSource.value) {
211            case PIDSourceParameter.kRate_val:
212                return getRate();
213            case PIDSourceParameter.kAngle_val:
214                return getAngle();
215            default:
216                return 0.0;
217            }
218        }
219    
220        /*
221         * Live Window code, only does anything if live window is activated.
222         */
223        public String getSmartDashboardType(){
224            return "Gyro";
225        }
226        private ITable m_table;
227        
228        /**
229         * {@inheritDoc}
230         */
231        public void initTable(ITable subtable) {
232            m_table = subtable;
233            updateTable();
234        }
235        
236        /**
237         * {@inheritDoc}
238         */
239        public ITable getTable(){
240            return m_table;
241        }
242        
243        /**
244         * {@inheritDoc}
245         */
246        public void updateTable() {
247            if (m_table != null) {
248                m_table.putNumber("Value", getAngle());
249            }
250        }
251        
252        /**
253         * {@inheritDoc}
254         */
255        public void startLiveWindowMode() {}
256        
257        /**
258         * {@inheritDoc}
259         */
260        public void stopLiveWindowMode() {}
261    }