blob: a95489bdf4dda387d7fc2351d3706b1f1c4e031b [file] [log] [blame]
brians343bc112013-02-10 01:53:46 +00001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2008. All Rights Reserved. */
3/* Open Source Software - may be modified and shared by FRC teams. The code */
4/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */
5/*----------------------------------------------------------------------------*/
6
7#include "PWM.h"
8
9#include "DigitalModule.h"
10#include "NetworkCommunication/UsageReporting.h"
11#include "Resource.h"
12#include "Utility.h"
13#include "WPIErrors.h"
14
Brian Silverman68749472014-01-05 14:50:00 -080015constexpr float PWM::kDefaultPwmPeriod;
16constexpr float PWM::kDefaultPwmCenter;
17const int32_t PWM::kDefaultPwmStepsDown;
18const int32_t PWM::kPwmDisabled;
brians343bc112013-02-10 01:53:46 +000019static Resource *allocated = NULL;
20
21/**
22 * Initialize PWMs given an module and channel.
23 *
24 * This method is private and is the common path for all the constructors for creating PWM
25 * instances. Checks module and channel value ranges and allocates the appropriate channel.
26 * The allocation is only done to help users ensure that they don't double assign channels.
27 */
Brian Silverman68749472014-01-05 14:50:00 -080028void PWM::InitPWM(uint8_t moduleNumber, uint32_t channel)
brians343bc112013-02-10 01:53:46 +000029{
Brian Silverman68749472014-01-05 14:50:00 -080030 m_table = NULL;
brians343bc112013-02-10 01:53:46 +000031 char buf[64];
32 Resource::CreateResourceObject(&allocated, tDIO::kNumSystems * kPwmChannels);
33 if (!CheckPWMModule(moduleNumber))
34 {
35 snprintf(buf, 64, "Digital Module %d", moduleNumber);
36 wpi_setWPIErrorWithContext(ModuleIndexOutOfRange, buf);
37 return;
38 }
39 if (!CheckPWMChannel(channel))
40 {
41 snprintf(buf, 64, "PWM Channel %d", channel);
42 wpi_setWPIErrorWithContext(ChannelIndexOutOfRange, buf);
43 return;
44 }
45
46 snprintf(buf, 64, "PWM %d (Module: %d)", channel, moduleNumber);
Brian Silverman362a7ad2013-04-17 15:28:29 -070047 if (allocated->Allocate((moduleNumber - 1) * kPwmChannels + channel - 1,
48 buf, this) == ~0ul)
brians343bc112013-02-10 01:53:46 +000049 {
brians343bc112013-02-10 01:53:46 +000050 return;
51 }
52 m_channel = channel;
53 m_module = DigitalModule::GetInstance(moduleNumber);
54 m_module->SetPWM(m_channel, kPwmDisabled);
55 m_eliminateDeadband = false;
56
57 nUsageReporting::report(nUsageReporting::kResourceType_PWM, channel, moduleNumber - 1);
58}
59
60/**
61 * Allocate a PWM given a module and channel.
62 * Allocate a PWM using a module and channel number.
63 *
64 * @param moduleNumber The digital module (1 or 2).
65 * @param channel The PWM channel on the digital module (1..10).
66 */
Brian Silverman68749472014-01-05 14:50:00 -080067PWM::PWM(uint8_t moduleNumber, uint32_t channel)
brians343bc112013-02-10 01:53:46 +000068 : m_module(NULL)
69{
70 InitPWM(moduleNumber, channel);
71}
72
73/**
74 * Allocate a PWM in the default module given a channel.
75 *
76 * Using a default module allocate a PWM given the channel number. The default module is the first
77 * slot numerically in the cRIO chassis.
78 *
79 * @param channel The PWM channel on the digital module.
80 */
Brian Silverman68749472014-01-05 14:50:00 -080081PWM::PWM(uint32_t channel)
brians343bc112013-02-10 01:53:46 +000082 : m_module(NULL)
83{
84 InitPWM(GetDefaultDigitalModule(), channel);
85}
86
87/**
88 * Free the PWM channel.
89 *
90 * Free the resource associated with the PWM channel and set the value to 0.
91 */
92PWM::~PWM()
93{
94 if (m_module)
95 {
96 m_module->SetPWM(m_channel, kPwmDisabled);
Brian Silverman362a7ad2013-04-17 15:28:29 -070097 allocated->Free((m_module->GetNumber() - 1) * kPwmChannels + m_channel - 1,
98 this);
brians343bc112013-02-10 01:53:46 +000099 }
100}
101
102/**
103 * Optionally eliminate the deadband from a speed controller.
104 * @param eliminateDeadband If true, set the motor curve on the Jaguar to eliminate
105 * the deadband in the middle of the range. Otherwise, keep the full range without
106 * modifying any values.
107 */
108void PWM::EnableDeadbandElimination(bool eliminateDeadband)
109{
110 if (StatusIsFatal()) return;
111 m_eliminateDeadband = eliminateDeadband;
112}
113
114/**
115 * Set the bounds on the PWM values.
116 * This sets the bounds on the PWM values for a particular each type of controller. The values
117 * determine the upper and lower speeds as well as the deadband bracket.
118 * @param max The Minimum pwm value
119 * @param deadbandMax The high end of the deadband range
120 * @param center The center speed (off)
121 * @param deadbandMin The low end of the deadband range
122 * @param min The minimum pwm value
123 */
Brian Silverman68749472014-01-05 14:50:00 -0800124void PWM::SetBounds(int32_t max, int32_t deadbandMax, int32_t center, int32_t deadbandMin, int32_t min)
brians343bc112013-02-10 01:53:46 +0000125{
126 if (StatusIsFatal()) return;
127 m_maxPwm = max;
128 m_deadbandMaxPwm = deadbandMax;
129 m_centerPwm = center;
130 m_deadbandMinPwm = deadbandMin;
131 m_minPwm = min;
132}
133
Brian Silverman68749472014-01-05 14:50:00 -0800134
135/**
136 * Set the bounds on the PWM pulse widths.
137 * This sets the bounds on the PWM values for a particular type of controller. The values
138 * determine the upper and lower speeds as well as the deadband bracket.
139 * @param max The max PWM pulse width in ms
140 * @param deadbandMax The high end of the deadband range pulse width in ms
141 * @param center The center (off) pulse width in ms
142 * @param deadbandMin The low end of the deadband pulse width in ms
143 * @param min The minimum pulse width in ms
144 */
145void PWM::SetBounds(double max, double deadbandMax, double center, double deadbandMin, double min)
146{
147 if (StatusIsFatal()) return;
148
149 double loopTime = m_module->GetLoopTiming()/(kSystemClockTicksPerMicrosecond*1e3);
150
151 m_maxPwm = (int32_t)((max-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);
152 m_deadbandMaxPwm = (int32_t)((deadbandMax-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);
153 m_centerPwm = (int32_t)((center-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);
154 m_deadbandMinPwm = (int32_t)((deadbandMin-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);
155 m_minPwm = (int32_t)((min-kDefaultPwmCenter)/loopTime+kDefaultPwmStepsDown-1);
156}
157
158uint32_t PWM::GetModuleNumber()
brians343bc112013-02-10 01:53:46 +0000159{
160 return m_module->GetNumber();
161}
162
163/**
164 * Set the PWM value based on a position.
165 *
166 * This is intended to be used by servos.
167 *
168 * @pre SetMaxPositivePwm() called.
169 * @pre SetMinNegativePwm() called.
170 *
171 * @param pos The position to set the servo between 0.0 and 1.0.
172 */
173void PWM::SetPosition(float pos)
174{
175 if (StatusIsFatal()) return;
176 if (pos < 0.0)
177 {
178 pos = 0.0;
179 }
180 else if (pos > 1.0)
181 {
182 pos = 1.0;
183 }
184
Brian Silverman68749472014-01-05 14:50:00 -0800185 int32_t rawValue;
brians343bc112013-02-10 01:53:46 +0000186 // note, need to perform the multiplication below as floating point before converting to int
Brian Silverman68749472014-01-05 14:50:00 -0800187 rawValue = (int32_t)( (pos * (float) GetFullRangeScaleFactor()) + GetMinNegativePwm());
brians343bc112013-02-10 01:53:46 +0000188
189 wpi_assert((rawValue >= GetMinNegativePwm()) && (rawValue <= GetMaxPositivePwm()));
190 wpi_assert(rawValue != kPwmDisabled);
191
192 // send the computed pwm value to the FPGA
Brian Silverman68749472014-01-05 14:50:00 -0800193 SetRaw((uint8_t)rawValue);
brians343bc112013-02-10 01:53:46 +0000194}
195
196/**
197 * Get the PWM value in terms of a position.
198 *
199 * This is intended to be used by servos.
200 *
201 * @pre SetMaxPositivePwm() called.
202 * @pre SetMinNegativePwm() called.
203 *
204 * @return The position the servo is set to between 0.0 and 1.0.
205 */
206float PWM::GetPosition()
207{
208 if (StatusIsFatal()) return 0.0;
Brian Silverman68749472014-01-05 14:50:00 -0800209 int32_t value = GetRaw();
brians343bc112013-02-10 01:53:46 +0000210 if (value < GetMinNegativePwm())
211 {
212 return 0.0;
213 }
214 else if (value > GetMaxPositivePwm())
215 {
216 return 1.0;
217 }
218 else
219 {
220 return (float)(value - GetMinNegativePwm()) / (float)GetFullRangeScaleFactor();
221 }
222}
223
224/**
225 * Set the PWM value based on a speed.
226 *
227 * This is intended to be used by speed controllers.
228 *
229 * @pre SetMaxPositivePwm() called.
230 * @pre SetMinPositivePwm() called.
231 * @pre SetCenterPwm() called.
232 * @pre SetMaxNegativePwm() called.
233 * @pre SetMinNegativePwm() called.
234 *
235 * @param speed The speed to set the speed controller between -1.0 and 1.0.
236 */
237void PWM::SetSpeed(float speed)
238{
239 if (StatusIsFatal()) return;
240 // clamp speed to be in the range 1.0 >= speed >= -1.0
241 if (speed < -1.0)
242 {
243 speed = -1.0;
244 }
245 else if (speed > 1.0)
246 {
247 speed = 1.0;
248 }
249
250 // calculate the desired output pwm value by scaling the speed appropriately
Brian Silverman68749472014-01-05 14:50:00 -0800251 int32_t rawValue;
brians343bc112013-02-10 01:53:46 +0000252 if (speed == 0.0)
253 {
254 rawValue = GetCenterPwm();
255 }
256 else if (speed > 0.0)
257 {
Brian Silverman68749472014-01-05 14:50:00 -0800258 rawValue = (int32_t)(speed * ((float)GetPositiveScaleFactor()) +
brians343bc112013-02-10 01:53:46 +0000259 ((float) GetMinPositivePwm()) + 0.5);
260 }
261 else
262 {
Brian Silverman68749472014-01-05 14:50:00 -0800263 rawValue = (int32_t)(speed * ((float)GetNegativeScaleFactor()) +
brians343bc112013-02-10 01:53:46 +0000264 ((float) GetMaxNegativePwm()) + 0.5);
265 }
266
267 // the above should result in a pwm_value in the valid range
268 wpi_assert((rawValue >= GetMinNegativePwm()) && (rawValue <= GetMaxPositivePwm()));
269 wpi_assert(rawValue != kPwmDisabled);
270
271 // send the computed pwm value to the FPGA
Brian Silverman68749472014-01-05 14:50:00 -0800272 SetRaw((uint8_t)rawValue);
brians343bc112013-02-10 01:53:46 +0000273}
274
275/**
276 * Get the PWM value in terms of speed.
277 *
278 * This is intended to be used by speed controllers.
279 *
280 * @pre SetMaxPositivePwm() called.
281 * @pre SetMinPositivePwm() called.
282 * @pre SetMaxNegativePwm() called.
283 * @pre SetMinNegativePwm() called.
284 *
285 * @return The most recently set speed between -1.0 and 1.0.
286 */
287float PWM::GetSpeed()
288{
289 if (StatusIsFatal()) return 0.0;
Brian Silverman68749472014-01-05 14:50:00 -0800290 int32_t value = GetRaw();
291 if (value == PWM::kPwmDisabled)
292 {
293 return 0.0;
294 }
295 else if (value > GetMaxPositivePwm())
brians343bc112013-02-10 01:53:46 +0000296 {
297 return 1.0;
298 }
299 else if (value < GetMinNegativePwm())
300 {
301 return -1.0;
302 }
303 else if (value > GetMinPositivePwm())
304 {
305 return (float)(value - GetMinPositivePwm()) / (float)GetPositiveScaleFactor();
306 }
307 else if (value < GetMaxNegativePwm())
308 {
309 return (float)(value - GetMaxNegativePwm()) / (float)GetNegativeScaleFactor();
310 }
311 else
312 {
313 return 0.0;
314 }
315}
316
317/**
318 * Set the PWM value directly to the hardware.
319 *
320 * Write a raw value to a PWM channel.
321 *
322 * @param value Raw PWM value. Range 0 - 255.
323 */
Brian Silverman68749472014-01-05 14:50:00 -0800324void PWM::SetRaw(uint8_t value)
brians343bc112013-02-10 01:53:46 +0000325{
326 if (StatusIsFatal()) return;
327 m_module->SetPWM(m_channel, value);
328}
329
330/**
331 * Get the PWM value directly from the hardware.
332 *
333 * Read a raw value from a PWM channel.
334 *
335 * @return Raw PWM control value. Range: 0 - 255.
336 */
Brian Silverman68749472014-01-05 14:50:00 -0800337uint8_t PWM::GetRaw()
brians343bc112013-02-10 01:53:46 +0000338{
339 if (StatusIsFatal()) return 0;
340 return m_module->GetPWM(m_channel);
341}
342
343/**
344 * Slow down the PWM signal for old devices.
345 *
346 * @param mult The period multiplier to apply to this channel
347 */
348void PWM::SetPeriodMultiplier(PeriodMultiplier mult)
349{
350 if (StatusIsFatal()) return;
351 switch(mult)
352 {
353 case kPeriodMultiplier_4X:
354 m_module->SetPWMPeriodScale(m_channel, 3); // Squelch 3 out of 4 outputs
355 break;
356 case kPeriodMultiplier_2X:
357 m_module->SetPWMPeriodScale(m_channel, 1); // Squelch 1 out of 2 outputs
358 break;
359 case kPeriodMultiplier_1X:
360 m_module->SetPWMPeriodScale(m_channel, 0); // Don't squelch any outputs
361 break;
362 default:
363 wpi_assert(false);
364 }
365}
briansab45cad2013-03-03 05:31:33 +0000366
367
368void PWM::ValueChanged(ITable* source, const std::string& key, EntryValue value, bool isNew) {
369 SetSpeed(value.f);
370}
371
372void PWM::UpdateTable() {
373 if (m_table != NULL) {
374 m_table->PutNumber("Value", GetSpeed());
375 }
376}
377
378void PWM::StartLiveWindowMode() {
Brian Silverman68749472014-01-05 14:50:00 -0800379 SetSpeed(0);
380 if (m_table != NULL) {
381 m_table->AddTableListener("Value", this, true);
382 }
briansab45cad2013-03-03 05:31:33 +0000383}
384
385void PWM::StopLiveWindowMode() {
386 SetSpeed(0);
Brian Silverman68749472014-01-05 14:50:00 -0800387 if (m_table != NULL) {
388 m_table->RemoveTableListener(this);
389 }
briansab45cad2013-03-03 05:31:33 +0000390}
391
392std::string PWM::GetSmartDashboardType() {
393 return "Speed Controller";
394}
395
396void PWM::InitTable(ITable *subTable) {
397 m_table = subTable;
398 UpdateTable();
399}
400
401ITable * PWM::GetTable() {
402 return m_table;
403}
404