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/*----------------------------------------------------------------------------*/
007package edu.wpi.first.wpilibj;
008
009import edu.wpi.first.wpilibj.communication.FRCNetworkCommunicationsLibrary.tResourceType;
010import edu.wpi.first.wpilibj.communication.UsageReporting;
011import edu.wpi.first.wpilibj.livewindow.LiveWindow;
012import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable;
013import edu.wpi.first.wpilibj.tables.ITable;
014import 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
019 * the robot rotates the new heading is computed by integrating the rate of
020 * rotation returned by the sensor. When the class is instantiated, it does a
021 * short calibration routine where it samples the gyro while at rest to
022 * determine the default offset. This is subtracted from each sample to
023 * determine the heading.
024 */
025public class Gyro extends SensorBase implements PIDSource, LiveWindowSendable {
026
027        static final int kOversampleBits = 10;
028        static final int kAverageBits = 0;
029        static final double kSamplesPerSecond = 50.0;
030        static final double kCalibrationSampleTime = 5.0;
031        static final double kDefaultVoltsPerDegreePerSecond = 0.007;
032        protected AnalogInput m_analog;
033        double m_voltsPerDegreePerSecond;
034        double m_offset;
035        int m_center;
036        boolean m_channelAllocated = false;
037        AccumulatorResult result;
038        private PIDSourceParameter m_pidSource;
039
040        /**
041         * Initialize the gyro. Calibrate the gyro by running for a number of
042         * samples and computing the center value. Then use the center
043         * value as the Accumulator center value for subsequent measurements. It's
044         * important to make sure that the robot is not moving while the centering
045         * calculations are in progress, this is typically done when the robot is
046         * first turned on while it's sitting at rest before the competition starts.
047         */
048        public void initGyro() {
049                result = new AccumulatorResult();
050                if (m_analog == null) {
051                        System.out.println("Null m_analog");
052                }
053                m_voltsPerDegreePerSecond = kDefaultVoltsPerDegreePerSecond;
054                m_analog.setAverageBits(kAverageBits);
055                m_analog.setOversampleBits(kOversampleBits);
056                double sampleRate = kSamplesPerSecond
057                                * (1 << (kAverageBits + kOversampleBits));
058                AnalogInput.setGlobalSampleRate(sampleRate);
059                Timer.delay(1.0);
060
061                m_analog.initAccumulator();
062                m_analog.resetAccumulator();
063
064                Timer.delay(kCalibrationSampleTime);
065
066                m_analog.getAccumulatorOutput(result);
067
068                m_center = (int) ((double) result.value / (double) result.count + .5);
069
070                m_offset = ((double) result.value / (double) result.count)
071                                - m_center;
072
073                m_analog.setAccumulatorCenter(m_center);
074                m_analog.resetAccumulator();
075
076                setDeadband(0.0);
077
078                setPIDSourceParameter(PIDSourceParameter.kAngle);
079
080                UsageReporting.report(tResourceType.kResourceType_Gyro, m_analog.getChannel());
081                LiveWindow.addSensor("Gyro", m_analog.getChannel(), this);
082        }
083
084        /**
085         * Gyro constructor using the channel number
086         *
087         * @param channel
088         *            The analog channel the gyro is connected to. Gyros can only 
089                  be used on on-board channels 0-1.
090         */
091        public Gyro(int channel) {
092                this(new AnalogInput(channel));
093                m_channelAllocated = true;
094        }
095
096        /**
097         * Gyro constructor with a precreated analog channel object. Use this
098         * constructor when the analog channel needs to be shared.
099         *
100         * @param channel
101         *            The AnalogInput object that the gyro is connected to. Gyros 
102                      can only be used on on-board channels 0-1.
103         */
104        public Gyro(AnalogInput channel) {
105                m_analog = channel;
106                if (m_analog == null) {
107                        throw new NullPointerException("AnalogInput supplied to Gyro constructor is null");
108                }
109                initGyro();
110        }
111
112        /**
113         * Reset the gyro. Resets the gyro to a heading of zero. This can be used if
114         * there is significant drift in the gyro and it needs to be recalibrated
115         * after it has been running.
116         */
117        public void reset() {
118                if (m_analog != null) {
119                        m_analog.resetAccumulator();
120                }
121        }
122
123        /**
124         * Delete (free) the accumulator and the analog components used for the
125         * gyro.
126         */
127        @Override
128        public void free() {
129                if (m_analog != null && m_channelAllocated) {
130                        m_analog.free();
131                }
132                m_analog = null;
133        }
134
135        /**
136         * Return the actual angle in degrees that the robot is currently facing.
137         *
138         * The angle is based on the current accumulator value corrected by the
139         * oversampling rate, the gyro type and the A/D calibration values. The
140         * angle is continuous, that is it will continue from 360 to 361 degrees. This allows
141         * algorithms that wouldn't want to see a discontinuity in the gyro output
142         * as it sweeps past from 360 to 0 on the second time around.
143         *
144         * @return the current heading of the robot in degrees. This heading is
145         *         based on integration of the returned rate from the gyro.
146         */
147        public double getAngle() {
148                if (m_analog == null) {
149                        return 0.0;
150                } else {
151                        m_analog.getAccumulatorOutput(result);
152
153                        long value = result.value - (long) (result.count * m_offset);
154
155                        double scaledValue = value
156                                        * 1e-9
157                                        * m_analog.getLSBWeight()
158                                        * (1 << m_analog.getAverageBits())
159                                        / (AnalogInput.getGlobalSampleRate() * m_voltsPerDegreePerSecond);
160
161                        return scaledValue;
162                }
163        }
164
165        /**
166         * Return the rate of rotation of the gyro
167         *
168         * The rate is based on the most recent reading of the gyro analog value
169         *
170         * @return the current rate in degrees per second
171         */
172        public double getRate() {
173                if (m_analog == null) {
174                        return 0.0;
175                } else {
176                        return (m_analog.getAverageValue() - (m_center + m_offset))
177                                        * 1e-9
178                                        * m_analog.getLSBWeight()
179                                        / ((1 << m_analog.getOversampleBits()) * m_voltsPerDegreePerSecond);
180                }
181        }
182
183        /**
184         * Set the gyro sensitivity. This takes the number of
185         * volts/degree/second sensitivity of the gyro and uses it in subsequent
186         * calculations to allow the code to work with multiple gyros. This value
187         * is typically found in the gyro datasheet.
188         *
189         * @param voltsPerDegreePerSecond
190         *            The sensitivity in Volts/degree/second.
191         */
192        public void setSensitivity(double voltsPerDegreePerSecond) {
193                m_voltsPerDegreePerSecond = voltsPerDegreePerSecond;
194        }
195
196        /**
197         * Set the size of the neutral zone.  Any voltage from the gyro less than
198         * this amount from the center is considered stationary.  Setting a
199         * deadband will decrease the amount of drift when the gyro isn't rotating,
200         * but will make it less accurate.
201         *
202         * @param volts The size of the deadband in volts
203         */
204        void setDeadband(double volts) {
205                int deadband = (int)(volts * 1e9 / m_analog.getLSBWeight() * (1 << m_analog.getOversampleBits()));
206                m_analog.setAccumulatorDeadband(deadband);
207        }
208
209        /**
210         * Set which parameter of the gyro you are using as a process control
211         * variable. The Gyro class supports the rate and angle parameters
212         *
213         * @param pidSource
214         *            An enum to select the parameter.
215         */
216        public void setPIDSourceParameter(PIDSourceParameter pidSource) {
217                BoundaryException.assertWithinBounds(pidSource.value, 1, 2);
218                m_pidSource = pidSource;
219        }
220
221        /**
222         * Get the output of the gyro for use with PIDControllers.
223         * May be the angle or rate depending on the set PIDSourceParameter
224         *
225         * @return the output according to the gyro
226         */
227        @Override
228        public double pidGet() {
229                switch (m_pidSource.value) {
230                case PIDSourceParameter.kRate_val:
231                        return getRate();
232                case PIDSourceParameter.kAngle_val:
233                        return getAngle();
234                default:
235                        return 0.0;
236                }
237        }
238
239        /*
240         * Live Window code, only does anything if live window is activated.
241         */
242        @Override
243        public String getSmartDashboardType() {
244                return "Gyro";
245        }
246
247        private ITable m_table;
248
249        /**
250         * {@inheritDoc}
251         */
252        @Override
253        public void initTable(ITable subtable) {
254                m_table = subtable;
255                updateTable();
256        }
257
258        /**
259         * {@inheritDoc}
260         */
261        @Override
262        public ITable getTable() {
263                return m_table;
264        }
265
266        /**
267         * {@inheritDoc}
268         */
269        @Override
270        public void updateTable() {
271                if (m_table != null) {
272                        m_table.putNumber("Value", getAngle());
273                }
274        }
275
276        /**
277         * {@inheritDoc}
278         */
279        @Override
280        public void startLiveWindowMode() {
281        }
282
283        /**
284         * {@inheritDoc}
285         */
286        @Override
287        public void stopLiveWindowMode() {
288        }
289}