blob: 06b527cd6ffbe39a31ae2c6eb3e10c63de83b91d [file] [log] [blame]
Brian Silverman41cdd3e2019-01-19 19:48:58 -08001/*----------------------------------------------------------------------------*/
2/* Copyright (c) 2016-2018 FIRST. 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 the root directory of */
5/* the project. */
6/*----------------------------------------------------------------------------*/
7
8#include "hal/PWM.h"
9
10#include <cmath>
11#include <thread>
12
13#include <wpi/raw_ostream.h>
14
15#include "ConstantsInternal.h"
16#include "DigitalInternal.h"
17#include "HALInitializer.h"
18#include "PortsInternal.h"
19#include "hal/cpp/fpga_clock.h"
20#include "hal/handles/HandlesInternal.h"
21
22using namespace hal;
23
24static inline int32_t GetMaxPositivePwm(DigitalPort* port) {
25 return port->maxPwm;
26}
27
28static inline int32_t GetMinPositivePwm(DigitalPort* port) {
29 if (port->eliminateDeadband) {
30 return port->deadbandMaxPwm;
31 } else {
32 return port->centerPwm + 1;
33 }
34}
35
36static inline int32_t GetCenterPwm(DigitalPort* port) {
37 return port->centerPwm;
38}
39
40static inline int32_t GetMaxNegativePwm(DigitalPort* port) {
41 if (port->eliminateDeadband) {
42 return port->deadbandMinPwm;
43 } else {
44 return port->centerPwm - 1;
45 }
46}
47
48static inline int32_t GetMinNegativePwm(DigitalPort* port) {
49 return port->minPwm;
50}
51
52static inline int32_t GetPositiveScaleFactor(DigitalPort* port) {
53 return GetMaxPositivePwm(port) - GetMinPositivePwm(port);
54} ///< The scale for positive speeds.
55
56static inline int32_t GetNegativeScaleFactor(DigitalPort* port) {
57 return GetMaxNegativePwm(port) - GetMinNegativePwm(port);
58} ///< The scale for negative speeds.
59
60static inline int32_t GetFullRangeScaleFactor(DigitalPort* port) {
61 return GetMaxPositivePwm(port) - GetMinNegativePwm(port);
62} ///< The scale for positions.
63
64namespace hal {
65namespace init {
66void InitializePWM() {}
67} // namespace init
68} // namespace hal
69
70extern "C" {
71
72HAL_DigitalHandle HAL_InitializePWMPort(HAL_PortHandle portHandle,
73 int32_t* status) {
74 hal::init::CheckInit();
75 initializeDigital(status);
76
77 if (*status != 0) return HAL_kInvalidHandle;
78
79 int16_t channel = getPortHandleChannel(portHandle);
80 if (channel == InvalidHandleIndex || channel >= kNumPWMChannels) {
81 *status = PARAMETER_OUT_OF_RANGE;
82 return HAL_kInvalidHandle;
83 }
84
85 uint8_t origChannel = static_cast<uint8_t>(channel);
86
87 if (origChannel < kNumPWMHeaders) {
88 channel += kNumDigitalChannels; // remap Headers to end of allocations
89 } else {
90 channel = remapMXPPWMChannel(channel) + 10; // remap MXP to proper channel
91 }
92
93 auto handle =
94 digitalChannelHandles->Allocate(channel, HAL_HandleEnum::PWM, status);
95
96 if (*status != 0)
97 return HAL_kInvalidHandle; // failed to allocate. Pass error back.
98
99 auto port = digitalChannelHandles->Get(handle, HAL_HandleEnum::PWM);
100 if (port == nullptr) { // would only occur on thread issue.
101 *status = HAL_HANDLE_ERROR;
102 return HAL_kInvalidHandle;
103 }
104
105 port->channel = origChannel;
106
107 if (port->channel > tPWM::kNumHdrRegisters - 1) {
108 int32_t bitToSet = 1 << remapMXPPWMChannel(port->channel);
109 uint16_t specialFunctions =
110 digitalSystem->readEnableMXPSpecialFunction(status);
111 digitalSystem->writeEnableMXPSpecialFunction(specialFunctions | bitToSet,
112 status);
113 }
114
115 // Defaults to allow an always valid config.
116 HAL_SetPWMConfig(handle, 2.0, 1.501, 1.5, 1.499, 1.0, status);
117
118 return handle;
119}
120void HAL_FreePWMPort(HAL_DigitalHandle pwmPortHandle, int32_t* status) {
121 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
122 if (port == nullptr) {
123 *status = HAL_HANDLE_ERROR;
124 return;
125 }
126
127 digitalChannelHandles->Free(pwmPortHandle, HAL_HandleEnum::PWM);
128
129 // Wait for no other object to hold this handle.
130 auto start = hal::fpga_clock::now();
131 while (port.use_count() != 1) {
132 auto current = hal::fpga_clock::now();
133 if (start + std::chrono::seconds(1) < current) {
134 wpi::outs() << "PWM handle free timeout\n";
135 wpi::outs().flush();
136 break;
137 }
138 std::this_thread::yield();
139 }
140
141 if (port->channel > tPWM::kNumHdrRegisters - 1) {
142 int32_t bitToUnset = 1 << remapMXPPWMChannel(port->channel);
143 uint16_t specialFunctions =
144 digitalSystem->readEnableMXPSpecialFunction(status);
145 digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToUnset,
146 status);
147 }
148}
149
150HAL_Bool HAL_CheckPWMChannel(int32_t channel) {
151 return channel < kNumPWMChannels && channel >= 0;
152}
153
154void HAL_SetPWMConfig(HAL_DigitalHandle pwmPortHandle, double max,
155 double deadbandMax, double center, double deadbandMin,
156 double min, int32_t* status) {
157 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
158 if (port == nullptr) {
159 *status = HAL_HANDLE_ERROR;
160 return;
161 }
162
163 // calculate the loop time in milliseconds
164 double loopTime =
165 HAL_GetPWMLoopTiming(status) / (kSystemClockTicksPerMicrosecond * 1e3);
166 if (*status != 0) return;
167
168 int32_t maxPwm = static_cast<int32_t>((max - kDefaultPwmCenter) / loopTime +
169 kDefaultPwmStepsDown - 1);
170 int32_t deadbandMaxPwm = static_cast<int32_t>(
171 (deadbandMax - kDefaultPwmCenter) / loopTime + kDefaultPwmStepsDown - 1);
172 int32_t centerPwm = static_cast<int32_t>(
173 (center - kDefaultPwmCenter) / loopTime + kDefaultPwmStepsDown - 1);
174 int32_t deadbandMinPwm = static_cast<int32_t>(
175 (deadbandMin - kDefaultPwmCenter) / loopTime + kDefaultPwmStepsDown - 1);
176 int32_t minPwm = static_cast<int32_t>((min - kDefaultPwmCenter) / loopTime +
177 kDefaultPwmStepsDown - 1);
178
179 port->maxPwm = maxPwm;
180 port->deadbandMaxPwm = deadbandMaxPwm;
181 port->deadbandMinPwm = deadbandMinPwm;
182 port->centerPwm = centerPwm;
183 port->minPwm = minPwm;
184 port->configSet = true;
185}
186
187void HAL_SetPWMConfigRaw(HAL_DigitalHandle pwmPortHandle, int32_t maxPwm,
188 int32_t deadbandMaxPwm, int32_t centerPwm,
189 int32_t deadbandMinPwm, int32_t minPwm,
190 int32_t* status) {
191 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
192 if (port == nullptr) {
193 *status = HAL_HANDLE_ERROR;
194 return;
195 }
196
197 port->maxPwm = maxPwm;
198 port->deadbandMaxPwm = deadbandMaxPwm;
199 port->deadbandMinPwm = deadbandMinPwm;
200 port->centerPwm = centerPwm;
201 port->minPwm = minPwm;
202}
203
204void HAL_GetPWMConfigRaw(HAL_DigitalHandle pwmPortHandle, int32_t* maxPwm,
205 int32_t* deadbandMaxPwm, int32_t* centerPwm,
206 int32_t* deadbandMinPwm, int32_t* minPwm,
207 int32_t* status) {
208 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
209 if (port == nullptr) {
210 *status = HAL_HANDLE_ERROR;
211 return;
212 }
213 *maxPwm = port->maxPwm;
214 *deadbandMaxPwm = port->deadbandMaxPwm;
215 *deadbandMinPwm = port->deadbandMinPwm;
216 *centerPwm = port->centerPwm;
217 *minPwm = port->minPwm;
218}
219
220void HAL_SetPWMEliminateDeadband(HAL_DigitalHandle pwmPortHandle,
221 HAL_Bool eliminateDeadband, int32_t* status) {
222 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
223 if (port == nullptr) {
224 *status = HAL_HANDLE_ERROR;
225 return;
226 }
227 port->eliminateDeadband = eliminateDeadband;
228}
229
230HAL_Bool HAL_GetPWMEliminateDeadband(HAL_DigitalHandle pwmPortHandle,
231 int32_t* status) {
232 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
233 if (port == nullptr) {
234 *status = HAL_HANDLE_ERROR;
235 return false;
236 }
237 return port->eliminateDeadband;
238}
239
240void HAL_SetPWMRaw(HAL_DigitalHandle pwmPortHandle, int32_t value,
241 int32_t* status) {
242 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
243 if (port == nullptr) {
244 *status = HAL_HANDLE_ERROR;
245 return;
246 }
247
248 if (port->channel < tPWM::kNumHdrRegisters) {
249 pwmSystem->writeHdr(port->channel, value, status);
250 } else {
251 pwmSystem->writeMXP(port->channel - tPWM::kNumHdrRegisters, value, status);
252 }
253}
254
255void HAL_SetPWMSpeed(HAL_DigitalHandle pwmPortHandle, double speed,
256 int32_t* status) {
257 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
258 if (port == nullptr) {
259 *status = HAL_HANDLE_ERROR;
260 return;
261 }
262 if (!port->configSet) {
263 *status = INCOMPATIBLE_STATE;
264 return;
265 }
266
267 DigitalPort* dPort = port.get();
268
269 if (speed < -1.0) {
270 speed = -1.0;
271 } else if (speed > 1.0) {
272 speed = 1.0;
273 } else if (!std::isfinite(speed)) {
274 speed = 0.0;
275 }
276
277 // calculate the desired output pwm value by scaling the speed appropriately
278 int32_t rawValue;
279 if (speed == 0.0) {
280 rawValue = GetCenterPwm(dPort);
281 } else if (speed > 0.0) {
282 rawValue = static_cast<int32_t>(
283 speed * static_cast<double>(GetPositiveScaleFactor(dPort)) +
284 static_cast<double>(GetMinPositivePwm(dPort)) + 0.5);
285 } else {
286 rawValue = static_cast<int32_t>(
287 speed * static_cast<double>(GetNegativeScaleFactor(dPort)) +
288 static_cast<double>(GetMaxNegativePwm(dPort)) + 0.5);
289 }
290
291 if (!((rawValue >= GetMinNegativePwm(dPort)) &&
292 (rawValue <= GetMaxPositivePwm(dPort))) ||
293 rawValue == kPwmDisabled) {
294 *status = HAL_PWM_SCALE_ERROR;
295 return;
296 }
297
298 HAL_SetPWMRaw(pwmPortHandle, rawValue, status);
299}
300
301void HAL_SetPWMPosition(HAL_DigitalHandle pwmPortHandle, double pos,
302 int32_t* status) {
303 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
304 if (port == nullptr) {
305 *status = HAL_HANDLE_ERROR;
306 return;
307 }
308 if (!port->configSet) {
309 *status = INCOMPATIBLE_STATE;
310 return;
311 }
312 DigitalPort* dPort = port.get();
313
314 if (pos < 0.0) {
315 pos = 0.0;
316 } else if (pos > 1.0) {
317 pos = 1.0;
318 }
319
320 // note, need to perform the multiplication below as floating point before
321 // converting to int
322 int32_t rawValue = static_cast<int32_t>(
323 (pos * static_cast<double>(GetFullRangeScaleFactor(dPort))) +
324 GetMinNegativePwm(dPort));
325
326 if (rawValue == kPwmDisabled) {
327 *status = HAL_PWM_SCALE_ERROR;
328 return;
329 }
330
331 HAL_SetPWMRaw(pwmPortHandle, rawValue, status);
332}
333
334void HAL_SetPWMDisabled(HAL_DigitalHandle pwmPortHandle, int32_t* status) {
335 HAL_SetPWMRaw(pwmPortHandle, kPwmDisabled, status);
336}
337
338int32_t HAL_GetPWMRaw(HAL_DigitalHandle pwmPortHandle, int32_t* status) {
339 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
340 if (port == nullptr) {
341 *status = HAL_HANDLE_ERROR;
342 return 0;
343 }
344
345 if (port->channel < tPWM::kNumHdrRegisters) {
346 return pwmSystem->readHdr(port->channel, status);
347 } else {
348 return pwmSystem->readMXP(port->channel - tPWM::kNumHdrRegisters, status);
349 }
350}
351
352double HAL_GetPWMSpeed(HAL_DigitalHandle pwmPortHandle, int32_t* status) {
353 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
354 if (port == nullptr) {
355 *status = HAL_HANDLE_ERROR;
356 return 0;
357 }
358 if (!port->configSet) {
359 *status = INCOMPATIBLE_STATE;
360 return 0;
361 }
362
363 int32_t value = HAL_GetPWMRaw(pwmPortHandle, status);
364 if (*status != 0) return 0;
365 DigitalPort* dPort = port.get();
366
367 if (value == kPwmDisabled) {
368 return 0.0;
369 } else if (value > GetMaxPositivePwm(dPort)) {
370 return 1.0;
371 } else if (value < GetMinNegativePwm(dPort)) {
372 return -1.0;
373 } else if (value > GetMinPositivePwm(dPort)) {
374 return static_cast<double>(value - GetMinPositivePwm(dPort)) /
375 static_cast<double>(GetPositiveScaleFactor(dPort));
376 } else if (value < GetMaxNegativePwm(dPort)) {
377 return static_cast<double>(value - GetMaxNegativePwm(dPort)) /
378 static_cast<double>(GetNegativeScaleFactor(dPort));
379 } else {
380 return 0.0;
381 }
382}
383
384double HAL_GetPWMPosition(HAL_DigitalHandle pwmPortHandle, int32_t* status) {
385 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
386 if (port == nullptr) {
387 *status = HAL_HANDLE_ERROR;
388 return 0;
389 }
390 if (!port->configSet) {
391 *status = INCOMPATIBLE_STATE;
392 return 0;
393 }
394
395 int32_t value = HAL_GetPWMRaw(pwmPortHandle, status);
396 if (*status != 0) return 0;
397 DigitalPort* dPort = port.get();
398
399 if (value < GetMinNegativePwm(dPort)) {
400 return 0.0;
401 } else if (value > GetMaxPositivePwm(dPort)) {
402 return 1.0;
403 } else {
404 return static_cast<double>(value - GetMinNegativePwm(dPort)) /
405 static_cast<double>(GetFullRangeScaleFactor(dPort));
406 }
407}
408
409void HAL_LatchPWMZero(HAL_DigitalHandle pwmPortHandle, int32_t* status) {
410 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
411 if (port == nullptr) {
412 *status = HAL_HANDLE_ERROR;
413 return;
414 }
415
416 pwmSystem->writeZeroLatch(port->channel, true, status);
417 pwmSystem->writeZeroLatch(port->channel, false, status);
418}
419
420void HAL_SetPWMPeriodScale(HAL_DigitalHandle pwmPortHandle, int32_t squelchMask,
421 int32_t* status) {
422 auto port = digitalChannelHandles->Get(pwmPortHandle, HAL_HandleEnum::PWM);
423 if (port == nullptr) {
424 *status = HAL_HANDLE_ERROR;
425 return;
426 }
427
428 if (port->channel < tPWM::kNumPeriodScaleHdrElements) {
429 pwmSystem->writePeriodScaleHdr(port->channel, squelchMask, status);
430 } else {
431 pwmSystem->writePeriodScaleMXP(
432 port->channel - tPWM::kNumPeriodScaleHdrElements, squelchMask, status);
433 }
434}
435
436int32_t HAL_GetPWMLoopTiming(int32_t* status) {
437 initializeDigital(status);
438 if (*status != 0) return 0;
439 return pwmSystem->readLoopTiming(status);
440}
441
442uint64_t HAL_GetPWMCycleStartTime(int32_t* status) {
443 initializeDigital(status);
444 if (*status != 0) return 0;
445
446 uint64_t upper1 = pwmSystem->readCycleStartTimeUpper(status);
447 uint32_t lower = pwmSystem->readCycleStartTime(status);
448 uint64_t upper2 = pwmSystem->readCycleStartTimeUpper(status);
449 if (*status != 0) return 0;
450 if (upper1 != upper2) {
451 // Rolled over between the lower call, reread lower
452 lower = pwmSystem->readCycleStartTime(status);
453 if (*status != 0) return 0;
454 }
455 return (upper2 << 32) + lower;
456}
457
458} // extern "C"