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