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