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    
008    package edu.wpi.first.wpilibj;
009    
010    import edu.wpi.first.wpilibj.communication.UsageReporting;
011    import edu.wpi.first.wpilibj.fpga.tDIO;
012    import edu.wpi.first.wpilibj.livewindow.LiveWindowSendable;
013    import edu.wpi.first.wpilibj.tables.ITable;
014    import edu.wpi.first.wpilibj.tables.ITableListener;
015    import edu.wpi.first.wpilibj.util.AllocationException;
016    import edu.wpi.first.wpilibj.util.CheckedAllocationException;
017    
018    /**
019     * Class implements the PWM generation in the FPGA.
020     * Values supplied as arguments for PWM outputs range from -1.0 to 1.0. They are mapped
021     * to the hardware dependent values, in this case 0-255 for the FPGA.
022     * Changes are immediately sent to the FPGA, and the update occurs at the next
023     * FPGA cycle. There is no delay.
024     *
025     * As of revision 0.1.4 of the FPGA, the FPGA interprets the 0-255 values as follows:
026     *   255 = full "forward"
027     *   254 to 129 = linear scaling from "full forward" to "center"
028     *   128 = center value
029     *   127 to 2 = linear scaling from "center" to "full reverse"
030     *   1 = full "reverse"
031     *   0 = disabled (i.e. PWM output is held low)
032     */
033    public class PWM extends SensorBase implements LiveWindowSendable {
034    
035        private static Resource allocated = new Resource((tDIO.kNumSystems * kPwmChannels));
036    
037        /**
038         * Represents the amount to multiply the minimum servo-pulse pwm period by.
039         */
040        public static class PeriodMultiplier {
041    
042            /**
043             * The integer value representing this enumeration
044             */
045            public final int value;
046            static final int k1X_val = 1;
047            static final int k2X_val = 2;
048            static final int k4X_val = 4;
049            /**
050             * Period Multiplier: don't skip pulses
051             */
052            public static final PeriodMultiplier k1X = new PeriodMultiplier(k1X_val);
053            /**
054             * Period Multiplier: skip every other pulse
055             */
056            public static final PeriodMultiplier k2X = new PeriodMultiplier(k2X_val);
057            /**
058             * Period Multiplier: skip three out of four pulses
059             */
060            public static final PeriodMultiplier k4X = new PeriodMultiplier(k4X_val);
061    
062            private PeriodMultiplier(int value) {
063                this.value = value;
064            }
065        }
066        private int m_channel;
067        private DigitalModule m_module;
068        /**
069         * kDefaultPwmPeriod is in ms
070         *
071         * - 20ms periods (50 Hz) are the "safest" setting in that this works for all devices
072         * - 20ms periods seem to be desirable for Vex Motors
073         * - 20ms periods are the specified period for HS-322HD servos, but work reliably down
074         *      to 10.0 ms; starting at about 8.5ms, the servo sometimes hums and get hot;
075         *      by 5.0ms the hum is nearly continuous
076         * - 10ms periods work well for Victor 884
077         * - 5ms periods allows higher update rates for Luminary Micro Jaguar speed controllers.
078         *      Due to the shipping firmware on the Jaguar, we can't run the update period less
079         *      than 5.05 ms.
080         *
081         * kDefaultPwmPeriod is the 1x period (5.05 ms).  In hardware, the period scaling is implemented as an
082         * output squelch to get longer periods for old devices.
083         */
084        protected static final double kDefaultPwmPeriod = 5.05;
085        /**
086         * kDefaultPwmCenter is the PWM range center in ms
087         */
088        protected static final double kDefaultPwmCenter = 1.5;
089        /**
090         * kDefaultPWMStepsDown is the number of PWM steps below the centerpoint
091         */
092        protected static final int kDefaultPwmStepsDown = 128;
093        public static final int kPwmDisabled = 0;
094        boolean m_eliminateDeadband;
095        int m_maxPwm;
096        int m_deadbandMaxPwm;
097        int m_centerPwm;
098        int m_deadbandMinPwm;
099        int m_minPwm;
100    
101        /**
102         * Initialize PWMs given an module and channel.
103         *
104         * This method is private and is the common path for all the constructors for creating PWM
105         * instances. Checks module and channel value ranges and allocates the appropriate channel.
106         * The allocation is only done to help users ensure that they don't double assign channels.
107         */
108        private void initPWM(final int moduleNumber, final int channel) {
109            checkPWMModule(moduleNumber);
110            checkPWMChannel(channel);
111            try {
112                allocated.allocate((moduleNumber - 1) * kPwmChannels + channel - 1);
113            } catch (CheckedAllocationException e) {
114                throw new AllocationException(
115                        "PWM channel " + channel + " on module " + moduleNumber + " is already allocated");
116            }
117            m_channel = channel;
118            m_module = DigitalModule.getInstance(moduleNumber);
119            m_module.setPWM(m_channel, kPwmDisabled);
120            m_eliminateDeadband = false;
121    
122            UsageReporting.report(UsageReporting.kResourceType_PWM, channel, moduleNumber-1);
123        }
124    
125        /**
126         * Allocate a PWM given a module and channel.
127         * Allocate a PWM using a module and channel number.
128         *
129         * @param moduleNumber The module number of the digital module to use.
130         * @param channel The PWM channel on the digital module.
131         */
132        public PWM(final int moduleNumber, final int channel) {
133            initPWM(moduleNumber, channel);
134        }
135    
136        /**
137         * Allocate a PWM in the default module given a channel.
138         *
139         * Using a default module allocate a PWM given the channel number.
140         *
141         * @param channel The PWM channel on the digital module.
142         */
143        public PWM(final int channel) {
144            initPWM(getDefaultDigitalModule(), channel);
145        }
146    
147        /**
148         * Free the PWM channel.
149         *
150         * Free the resource associated with the PWM channel and set the value to 0.
151         */
152        public void free() {
153            m_module.setPWM(m_channel, kPwmDisabled);
154            allocated.free((m_module.getModuleNumber() - 1) * kPwmChannels + m_channel - 1);
155        }
156    
157        /**
158         * Optionally eliminate the deadband from a speed controller.
159         * @param eliminateDeadband If true, set the motor curve on the Jaguar to eliminate
160         * the deadband in the middle of the range. Otherwise, keep the full range without
161         * modifying any values.
162         */
163        public void enableDeadbandElimination(boolean eliminateDeadband) {
164            m_eliminateDeadband = eliminateDeadband;
165        }
166    
167        /**
168         * Set the bounds on the PWM values.
169         * This sets the bounds on the PWM values for a particular each type of controller. The values
170         * determine the upper and lower speeds as well as the deadband bracket.
171         * @deprecated Recommended to set bounds in ms using {@link #setBounds(double, double, double, double, double)}
172         * @param max The Minimum pwm value
173         * @param deadbandMax The high end of the deadband range
174         * @param center The center speed (off)
175         * @param deadbandMin The low end of the deadband range
176         * @param min The minimum pwm value
177         */
178        public void setBounds(final int max, final int deadbandMax, final int center, final int deadbandMin, final int min) {
179            m_maxPwm = max;
180            m_deadbandMaxPwm = deadbandMax;
181            m_centerPwm = center;
182            m_deadbandMinPwm = deadbandMin;
183            m_minPwm = min;
184        }
185        
186        /**
187        * Set the bounds on the PWM pulse widths.
188        * This sets the bounds on the PWM values for a particular type of controller. The values
189        * determine the upper and lower speeds as well as the deadband bracket.
190        * @param max The max PWM pulse width in ms
191        * @param deadbandMax The high end of the deadband range pulse width in ms
192        * @param center The center (off) pulse width in ms
193        * @param deadbandMin The low end of the deadband pulse width in ms
194        * @param min The minimum pulse width in ms
195        */
196       void setBounds(double max, double deadbandMax, double center, double deadbandMin, double min)
197       {
198               double loopTime = m_module.getLoopTiming()/(kSystemClockTicksPerMicrosecond*1e3);
199    
200               m_maxPwm = (int)((max-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);
201               m_deadbandMaxPwm = (int)((deadbandMax-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);
202               m_centerPwm = (int)((center-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);
203               m_deadbandMinPwm = (int)((deadbandMin-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);
204               m_minPwm = (int)((min-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);   
205       }
206        
207        /**
208         * Gets the module number associated with the PWM Object.
209         *
210         * @return The module's number.
211         */
212        public int getModuleNumber(){
213            return m_module.getModuleNumber();
214        }
215        
216        /**
217         * Gets the channel number associated with the PWM Object.
218         *
219         * @return The channel number.
220         */
221        public int getChannel(){
222            return m_channel;
223        }
224    
225        /**
226         * Set the PWM value based on a position.
227         *
228         * This is intended to be used by servos.
229         *
230         * @pre SetMaxPositivePwm() called.
231         * @pre SetMinNegativePwm() called.
232         *
233         * @param pos The position to set the servo between 0.0 and 1.0.
234         */
235        public void setPosition(double pos) {
236            if (pos < 0.0) {
237                pos = 0.0;
238            } else if (pos > 1.0) {
239                pos = 1.0;
240            }
241    
242            int rawValue;
243            // note, need to perform the multiplication below as floating point before converting to int
244            rawValue = (int) ((pos * (double)getFullRangeScaleFactor()) + getMinNegativePwm());
245    
246            // send the computed pwm value to the FPGA
247            setRaw(rawValue);
248        }
249    
250        /**
251         * Get the PWM value in terms of a position.
252         *
253         * This is intended to be used by servos.
254         *
255         * @pre SetMaxPositivePwm() called.
256         * @pre SetMinNegativePwm() called.
257         *
258         * @return The position the servo is set to between 0.0 and 1.0.
259         */
260        public double getPosition() {
261            int value = getRaw();
262            if (value < getMinNegativePwm()) {
263                return 0.0;
264            } else if (value > getMaxPositivePwm()) {
265                return 1.0;
266            } else {
267                return (double)(value - getMinNegativePwm()) / (double)getFullRangeScaleFactor();
268            }
269        }
270    
271        /**
272         * Set the PWM value based on a speed.
273         *
274         * This is intended to be used by speed controllers.
275         *
276         * @pre SetMaxPositivePwm() called.
277         * @pre SetMinPositivePwm() called.
278         * @pre SetCenterPwm() called.
279         * @pre SetMaxNegativePwm() called.
280         * @pre SetMinNegativePwm() called.
281         *
282         * @param speed The speed to set the speed controller between -1.0 and 1.0.
283         */
284        final void setSpeed(double speed) {
285            // clamp speed to be in the range 1.0 >= speed >= -1.0
286            if (speed < -1.0) {
287                speed = -1.0;
288            } else if (speed > 1.0) {
289                speed = 1.0;
290            }
291    
292            // calculate the desired output pwm value by scaling the speed appropriately
293            int rawValue;
294            if (speed == 0.0) {
295                rawValue = getCenterPwm();
296            } else if (speed > 0.0) {
297                rawValue = (int) (speed * ((double)getPositiveScaleFactor()) +
298                                  ((double)getMinPositivePwm()) + 0.5);
299            } else {
300                rawValue = (int) (speed * ((double)getNegativeScaleFactor()) +
301                                  ((double)getMaxNegativePwm()) + 0.5);
302            }
303    
304            // send the computed pwm value to the FPGA
305            setRaw(rawValue);
306        }
307    
308        /**
309         * Get the PWM value in terms of speed.
310         *
311         * This is intended to be used by speed controllers.
312         *
313         * @pre SetMaxPositivePwm() called.
314         * @pre SetMinPositivePwm() called.
315         * @pre SetMaxNegativePwm() called.
316         * @pre SetMinNegativePwm() called.
317         *
318         * @return The most recently set speed between -1.0 and 1.0.
319         */
320        public double getSpeed() {
321            int value = getRaw();
322            if (value > getMaxPositivePwm()) {
323                return 1.0;
324            } else if (value < getMinNegativePwm()) {
325                return -1.0;
326            } else if (value > getMinPositivePwm()) {
327                return (double) (value - getMinPositivePwm()) / (double)getPositiveScaleFactor();
328            } else if (value < getMaxNegativePwm()) {
329                return (double) (value - getMaxNegativePwm()) / (double)getNegativeScaleFactor();
330            } else {
331                return 0.0;
332            }
333        }
334    
335        /**
336         * Set the PWM value directly to the hardware.
337         *
338         * Write a raw value to a PWM channel.
339         *
340         * @param value Raw PWM value.  Range 0 - 255.
341         */
342        public void setRaw(int value) {
343            m_module.setPWM(m_channel, value);
344        }
345    
346        /**
347         * Get the PWM value directly from the hardware.
348         *
349         * Read a raw value from a PWM channel.
350         *
351         * @return Raw PWM control value.  Range: 0 - 255.
352         */
353        public int getRaw() {
354            return m_module.getPWM(m_channel);
355        }
356    
357        /**
358         * Slow down the PWM signal for old devices.
359         *
360         * @param mult The period multiplier to apply to this channel
361         */
362        public void setPeriodMultiplier(PeriodMultiplier mult) {
363            switch (mult.value) {
364                case PeriodMultiplier.k4X_val:
365                    m_module.setPWMPeriodScale(m_channel, 3); // Squelch 3 out of 4 outputs
366                    break;
367                case PeriodMultiplier.k2X_val:
368                    m_module.setPWMPeriodScale(m_channel, 1); // Squelch 1 out of 2 outputs
369                    break;
370                case PeriodMultiplier.k1X_val:
371                    m_module.setPWMPeriodScale(m_channel, 0); // Don't squelch any outputs
372                    break;
373                default:
374                //Cannot hit this, limited by PeriodMultiplier enum
375            }
376        }
377    
378        private int getMaxPositivePwm() {
379            return m_maxPwm;
380        }
381    
382        ;
383    
384        private int getMinPositivePwm() {
385            return m_eliminateDeadband ? m_deadbandMaxPwm : m_centerPwm + 1;
386        }
387    
388        ;
389    
390        private int getCenterPwm() {
391            return m_centerPwm;
392        }
393    
394        ;
395    
396        private int getMaxNegativePwm() {
397            return m_eliminateDeadband ? m_deadbandMinPwm : m_centerPwm - 1;
398        }
399    
400        ;
401    
402        private int getMinNegativePwm() {
403            return m_minPwm;
404        }
405    
406        ;
407    
408        private int getPositiveScaleFactor() {
409            return getMaxPositivePwm() - getMinPositivePwm();
410        } ///< The scale for positive speeds.
411    
412        private int getNegativeScaleFactor() {
413            return getMaxNegativePwm() - getMinNegativePwm();
414        } ///< The scale for negative speeds.
415    
416        private int getFullRangeScaleFactor() {
417            return getMaxPositivePwm() - getMinNegativePwm();
418        } ///< The scale for positions.
419    
420        /*
421         * Live Window code, only does anything if live window is activated.
422         */
423        public String getSmartDashboardType(){
424            return "Speed Controller";
425        }
426        private ITable m_table;
427        private ITableListener m_table_listener;
428        
429        /**
430         * {@inheritDoc}
431         */
432        public void initTable(ITable subtable) {
433            m_table = subtable;
434            updateTable();
435        }
436        
437        /**
438         * {@inheritDoc}
439         */
440        public void updateTable() {
441            if (m_table != null) {
442                m_table.putNumber("Value", getSpeed());
443            }
444        }
445        
446        /**
447         * {@inheritDoc}
448         */
449        public ITable getTable(){
450            return m_table;
451        }
452        
453        /**
454         * {@inheritDoc}
455         */
456        public void startLiveWindowMode() {
457            setSpeed(0); // Stop for safety
458            m_table_listener = new ITableListener() {
459                public void valueChanged(ITable itable, String key, Object value, boolean bln) {
460                    setSpeed(((Double) value).doubleValue());
461                }
462            };
463            m_table.addTableListener("Value", m_table_listener, true);
464        }
465        
466        /**
467         * {@inheritDoc}
468         */
469        public void stopLiveWindowMode() {
470            setSpeed(0); // Stop for safety
471            // TODO: Broken, should only remove the listener from "Value" only.
472            m_table.removeTableListener(m_table_listener);
473        }
474    }