blob: 7b26dd16b183e3b442707af36f870abb0ee7175f [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/DIO.h"
6
7#include <cmath>
Austin Schuh812d0d12021-11-04 20:16:48 -07008#include <cstdio>
Brian Silverman8fce7482020-01-05 13:18:21 -08009#include <thread>
10
Brian Silverman8fce7482020-01-05 13:18:21 -080011#include "DigitalInternal.h"
12#include "HALInitializer.h"
Austin Schuh812d0d12021-11-04 20:16:48 -070013#include "HALInternal.h"
Brian Silverman8fce7482020-01-05 13:18:21 -080014#include "PortsInternal.h"
15#include "hal/cpp/fpga_clock.h"
16#include "hal/handles/HandlesInternal.h"
17#include "hal/handles/LimitedHandleResource.h"
18
19using namespace hal;
20
21// Create a mutex to protect changes to the DO PWM config
22static wpi::mutex digitalPwmMutex;
23
24static LimitedHandleResource<HAL_DigitalPWMHandle, uint8_t,
25 kNumDigitalPWMOutputs, HAL_HandleEnum::DigitalPWM>*
26 digitalPWMHandles;
27
Austin Schuh812d0d12021-11-04 20:16:48 -070028namespace hal::init {
Brian Silverman8fce7482020-01-05 13:18:21 -080029void InitializeDIO() {
30 static LimitedHandleResource<HAL_DigitalPWMHandle, uint8_t,
31 kNumDigitalPWMOutputs,
32 HAL_HandleEnum::DigitalPWM>
33 dpH;
34 digitalPWMHandles = &dpH;
35}
Austin Schuh812d0d12021-11-04 20:16:48 -070036} // namespace hal::init
Brian Silverman8fce7482020-01-05 13:18:21 -080037
38extern "C" {
39
40HAL_DigitalHandle HAL_InitializeDIOPort(HAL_PortHandle portHandle,
Austin Schuh812d0d12021-11-04 20:16:48 -070041 HAL_Bool input,
42 const char* allocationLocation,
43 int32_t* status) {
Brian Silverman8fce7482020-01-05 13:18:21 -080044 hal::init::CheckInit();
45 initializeDigital(status);
46
Austin Schuh812d0d12021-11-04 20:16:48 -070047 if (*status != 0) {
Brian Silverman8fce7482020-01-05 13:18:21 -080048 return HAL_kInvalidHandle;
49 }
50
Austin Schuh812d0d12021-11-04 20:16:48 -070051 int16_t channel = getPortHandleChannel(portHandle);
52 if (channel == InvalidHandleIndex || channel >= kNumDigitalChannels) {
53 *status = RESOURCE_OUT_OF_RANGE;
54 hal::SetLastErrorIndexOutOfRange(status, "Invalid Index for DIO", 0,
55 kNumDigitalChannels, channel);
Brian Silverman8fce7482020-01-05 13:18:21 -080056 return HAL_kInvalidHandle;
57 }
58
Austin Schuh812d0d12021-11-04 20:16:48 -070059 HAL_DigitalHandle handle;
60
61 auto port = digitalChannelHandles->Allocate(channel, HAL_HandleEnum::DIO,
62 &handle, status);
63
64 if (*status != 0) {
65 if (port) {
66 hal::SetLastErrorPreviouslyAllocated(status, "PWM or DIO", channel,
67 port->previousAllocation);
68 } else {
69 hal::SetLastErrorIndexOutOfRange(status, "Invalid Index for DIO", 0,
70 kNumDigitalChannels, channel);
71 }
72 return HAL_kInvalidHandle; // failed to allocate. Pass error back.
73 }
74
Brian Silverman8fce7482020-01-05 13:18:21 -080075 port->channel = static_cast<uint8_t>(channel);
76
77 std::scoped_lock lock(digitalDIOMutex);
78
79 tDIO::tOutputEnable outputEnable = digitalSystem->readOutputEnable(status);
80
81 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
82 if (!getPortHandleSPIEnable(portHandle)) {
83 // if this flag is not set, we actually want DIO.
84 uint32_t bitToSet = 1u << remapSPIChannel(port->channel);
85
86 uint16_t specialFunctions = spiSystem->readEnableDIO(status);
87 // Set the field to enable SPI DIO
88 spiSystem->writeEnableDIO(specialFunctions | bitToSet, status);
89
90 if (input) {
91 outputEnable.SPIPort =
92 outputEnable.SPIPort & (~bitToSet); // clear the field for read
93 } else {
94 outputEnable.SPIPort =
95 outputEnable.SPIPort | bitToSet; // set the bits for write
96 }
97 }
98 } else if (port->channel < kNumDigitalHeaders) {
99 uint32_t bitToSet = 1u << port->channel;
100 if (input) {
101 outputEnable.Headers =
102 outputEnable.Headers & (~bitToSet); // clear the bit for read
103 } else {
104 outputEnable.Headers =
105 outputEnable.Headers | bitToSet; // set the bit for write
106 }
107 } else {
108 uint32_t bitToSet = 1u << remapMXPChannel(port->channel);
109
110 uint16_t specialFunctions =
111 digitalSystem->readEnableMXPSpecialFunction(status);
112 digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToSet,
113 status);
114
115 if (input) {
116 outputEnable.MXP =
117 outputEnable.MXP & (~bitToSet); // clear the bit for read
118 } else {
119 outputEnable.MXP = outputEnable.MXP | bitToSet; // set the bit for write
120 }
121 }
122
123 digitalSystem->writeOutputEnable(outputEnable, status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700124 port->previousAllocation = allocationLocation ? allocationLocation : "";
Brian Silverman8fce7482020-01-05 13:18:21 -0800125
126 return handle;
127}
128
129HAL_Bool HAL_CheckDIOChannel(int32_t channel) {
130 return channel < kNumDigitalChannels && channel >= 0;
131}
132
133void HAL_FreeDIOPort(HAL_DigitalHandle dioPortHandle) {
134 auto port = digitalChannelHandles->Get(dioPortHandle, HAL_HandleEnum::DIO);
135 // no status, so no need to check for a proper free.
Austin Schuh812d0d12021-11-04 20:16:48 -0700136 if (port == nullptr)
137 return;
Brian Silverman8fce7482020-01-05 13:18:21 -0800138 digitalChannelHandles->Free(dioPortHandle, HAL_HandleEnum::DIO);
139
140 // Wait for no other object to hold this handle.
141 auto start = hal::fpga_clock::now();
142 while (port.use_count() != 1) {
143 auto current = hal::fpga_clock::now();
144 if (start + std::chrono::seconds(1) < current) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700145 std::puts("DIO handle free timeout");
146 std::fflush(stdout);
Brian Silverman8fce7482020-01-05 13:18:21 -0800147 break;
148 }
149 std::this_thread::yield();
150 }
151
152 int32_t status = 0;
153 std::scoped_lock lock(digitalDIOMutex);
154 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
155 // Unset the SPI flag
156 int32_t bitToUnset = 1 << remapSPIChannel(port->channel);
157 uint16_t specialFunctions = spiSystem->readEnableDIO(&status);
158 spiSystem->writeEnableDIO(specialFunctions & ~bitToUnset, &status);
159 } else if (port->channel >= kNumDigitalHeaders) {
160 // Unset the MXP flag
161 uint32_t bitToUnset = 1u << remapMXPChannel(port->channel);
162
163 uint16_t specialFunctions =
164 digitalSystem->readEnableMXPSpecialFunction(&status);
165 digitalSystem->writeEnableMXPSpecialFunction(specialFunctions | bitToUnset,
166 &status);
167 }
168}
169
170void HAL_SetDIOSimDevice(HAL_DigitalHandle handle, HAL_SimDeviceHandle device) {
171}
172
173HAL_DigitalPWMHandle HAL_AllocateDigitalPWM(int32_t* status) {
174 auto handle = digitalPWMHandles->Allocate();
175 if (handle == HAL_kInvalidHandle) {
176 *status = NO_AVAILABLE_RESOURCES;
177 return HAL_kInvalidHandle;
178 }
179
180 auto id = digitalPWMHandles->Get(handle);
181 if (id == nullptr) { // would only occur on thread issue.
182 *status = HAL_HANDLE_ERROR;
183 return HAL_kInvalidHandle;
184 }
185 *id = static_cast<uint8_t>(getHandleIndex(handle));
186
187 return handle;
188}
189
190void HAL_FreeDigitalPWM(HAL_DigitalPWMHandle pwmGenerator, int32_t* status) {
191 digitalPWMHandles->Free(pwmGenerator);
192}
193
194void HAL_SetDigitalPWMRate(double rate, int32_t* status) {
195 // Currently rounding in the log rate domain... heavy weight toward picking a
196 // higher freq.
197 // TODO: Round in the linear rate domain.
198 initializeDigital(status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700199 if (*status != 0) {
200 return;
201 }
202 uint16_t pwmPeriodPower =
203 std::lround(std::log(1.0 / (16 * 1.0E-6 * rate)) / std::log(2.0));
Brian Silverman8fce7482020-01-05 13:18:21 -0800204 digitalSystem->writePWMPeriodPower(pwmPeriodPower, status);
205}
206
207void HAL_SetDigitalPWMDutyCycle(HAL_DigitalPWMHandle pwmGenerator,
208 double dutyCycle, int32_t* status) {
209 auto port = digitalPWMHandles->Get(pwmGenerator);
210 if (port == nullptr) {
211 *status = HAL_HANDLE_ERROR;
212 return;
213 }
214 int32_t id = *port;
Austin Schuh812d0d12021-11-04 20:16:48 -0700215 if (dutyCycle > 1.0) {
216 dutyCycle = 1.0;
217 }
218 if (dutyCycle < 0.0) {
219 dutyCycle = 0.0;
220 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800221 double rawDutyCycle = 256.0 * dutyCycle;
Austin Schuh812d0d12021-11-04 20:16:48 -0700222 if (rawDutyCycle > 255.5) {
223 rawDutyCycle = 255.5;
224 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800225 {
226 std::scoped_lock lock(digitalPwmMutex);
227 uint16_t pwmPeriodPower = digitalSystem->readPWMPeriodPower(status);
228 if (pwmPeriodPower < 4) {
229 // The resolution of the duty cycle drops close to the highest
230 // frequencies.
231 rawDutyCycle = rawDutyCycle / std::pow(2.0, 4 - pwmPeriodPower);
232 }
233 if (id < 4)
234 digitalSystem->writePWMDutyCycleA(id, static_cast<uint8_t>(rawDutyCycle),
235 status);
236 else
237 digitalSystem->writePWMDutyCycleB(
238 id - 4, static_cast<uint8_t>(rawDutyCycle), status);
239 }
240}
241
242void HAL_SetDigitalPWMOutputChannel(HAL_DigitalPWMHandle pwmGenerator,
243 int32_t channel, int32_t* status) {
244 auto port = digitalPWMHandles->Get(pwmGenerator);
245 if (port == nullptr) {
246 *status = HAL_HANDLE_ERROR;
247 return;
248 }
249 int32_t id = *port;
250 if (channel >= kNumDigitalHeaders &&
251 channel <
252 kNumDigitalHeaders + kNumDigitalMXPChannels) { // If it is on the MXP
253 /* Then to write as a digital PWM channel an offset is needed to write on
254 * the correct channel
255 */
256 channel += kMXPDigitalPWMOffset;
257 }
258 digitalSystem->writePWMOutputSelect(id, channel, status);
259}
260
261void HAL_SetDIO(HAL_DigitalHandle dioPortHandle, HAL_Bool value,
262 int32_t* status) {
263 auto port = digitalChannelHandles->Get(dioPortHandle, HAL_HandleEnum::DIO);
264 if (port == nullptr) {
265 *status = HAL_HANDLE_ERROR;
266 return;
267 }
268 if (value != 0 && value != 1) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700269 if (value != 0) {
270 value = 1;
271 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800272 }
273 {
274 std::scoped_lock lock(digitalDIOMutex);
Austin Schuh812d0d12021-11-04 20:16:48 -0700275
276 tDIO::tOutputEnable currentOutputEnable =
277 digitalSystem->readOutputEnable(status);
278
279 HAL_Bool isInput = false;
280
281 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
282 isInput =
283 ((currentOutputEnable.SPIPort >> remapSPIChannel(port->channel)) &
284 1) == 0;
285 } else if (port->channel < kNumDigitalHeaders) {
286 isInput = ((currentOutputEnable.Headers >> port->channel) & 1) == 0;
287 } else {
288 isInput = ((currentOutputEnable.MXP >> remapMXPChannel(port->channel)) &
289 1) == 0;
290 }
291
292 if (isInput) {
293 *status = PARAMETER_OUT_OF_RANGE;
294 hal::SetLastError(status, "Cannot set output of an input channel");
295 return;
296 }
297
Brian Silverman8fce7482020-01-05 13:18:21 -0800298 tDIO::tDO currentDIO = digitalSystem->readDO(status);
299
300 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
301 if (value == 0) {
302 currentDIO.SPIPort =
303 currentDIO.SPIPort & ~(1u << remapSPIChannel(port->channel));
304 } else if (value == 1) {
305 currentDIO.SPIPort =
306 currentDIO.SPIPort | (1u << remapSPIChannel(port->channel));
307 }
308 } else if (port->channel < kNumDigitalHeaders) {
309 if (value == 0) {
310 currentDIO.Headers = currentDIO.Headers & ~(1u << port->channel);
311 } else if (value == 1) {
312 currentDIO.Headers = currentDIO.Headers | (1u << port->channel);
313 }
314 } else {
315 if (value == 0) {
316 currentDIO.MXP =
317 currentDIO.MXP & ~(1u << remapMXPChannel(port->channel));
318 } else if (value == 1) {
319 currentDIO.MXP =
320 currentDIO.MXP | (1u << remapMXPChannel(port->channel));
321 }
322 }
323 digitalSystem->writeDO(currentDIO, status);
324 }
325}
326
327void HAL_SetDIODirection(HAL_DigitalHandle dioPortHandle, HAL_Bool input,
328 int32_t* status) {
329 auto port = digitalChannelHandles->Get(dioPortHandle, HAL_HandleEnum::DIO);
330 if (port == nullptr) {
331 *status = HAL_HANDLE_ERROR;
332 return;
333 }
334 {
335 std::scoped_lock lock(digitalDIOMutex);
336 tDIO::tOutputEnable currentDIO = digitalSystem->readOutputEnable(status);
337
338 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
339 if (input) {
340 currentDIO.SPIPort =
341 currentDIO.SPIPort & ~(1u << remapSPIChannel(port->channel));
342 } else {
343 currentDIO.SPIPort =
344 currentDIO.SPIPort | (1u << remapSPIChannel(port->channel));
345 }
346 } else if (port->channel < kNumDigitalHeaders) {
347 if (input) {
348 currentDIO.Headers = currentDIO.Headers & ~(1u << port->channel);
349 } else {
350 currentDIO.Headers = currentDIO.Headers | (1u << port->channel);
351 }
352 } else {
353 if (input) {
354 currentDIO.MXP =
355 currentDIO.MXP & ~(1u << remapMXPChannel(port->channel));
356 } else {
357 currentDIO.MXP =
358 currentDIO.MXP | (1u << remapMXPChannel(port->channel));
359 }
360 }
361 digitalSystem->writeOutputEnable(currentDIO, status);
362 }
363}
364
365HAL_Bool HAL_GetDIO(HAL_DigitalHandle dioPortHandle, int32_t* status) {
366 auto port = digitalChannelHandles->Get(dioPortHandle, HAL_HandleEnum::DIO);
367 if (port == nullptr) {
368 *status = HAL_HANDLE_ERROR;
369 return false;
370 }
371 tDIO::tDI currentDIO = digitalSystem->readDI(status);
372 // Shift 00000001 over channel-1 places.
373 // AND it against the currentDIO
374 // if it == 0, then return false
375 // else return true
376
377 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
378 return ((currentDIO.SPIPort >> remapSPIChannel(port->channel)) & 1) != 0;
379 } else if (port->channel < kNumDigitalHeaders) {
380 return ((currentDIO.Headers >> port->channel) & 1) != 0;
381 } else {
382 return ((currentDIO.MXP >> remapMXPChannel(port->channel)) & 1) != 0;
383 }
384}
385
386HAL_Bool HAL_GetDIODirection(HAL_DigitalHandle dioPortHandle, int32_t* status) {
387 auto port = digitalChannelHandles->Get(dioPortHandle, HAL_HandleEnum::DIO);
388 if (port == nullptr) {
389 *status = HAL_HANDLE_ERROR;
390 return false;
391 }
392 tDIO::tOutputEnable currentOutputEnable =
393 digitalSystem->readOutputEnable(status);
394 // Shift 00000001 over port->channel-1 places.
395 // AND it against the currentOutputEnable
396 // if it == 0, then return false
397 // else return true
398
399 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
400 return ((currentOutputEnable.SPIPort >> remapSPIChannel(port->channel)) &
Austin Schuh812d0d12021-11-04 20:16:48 -0700401 1) == 0;
Brian Silverman8fce7482020-01-05 13:18:21 -0800402 } else if (port->channel < kNumDigitalHeaders) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700403 return ((currentOutputEnable.Headers >> port->channel) & 1) == 0;
Brian Silverman8fce7482020-01-05 13:18:21 -0800404 } else {
Austin Schuh812d0d12021-11-04 20:16:48 -0700405 return ((currentOutputEnable.MXP >> remapMXPChannel(port->channel)) & 1) ==
Brian Silverman8fce7482020-01-05 13:18:21 -0800406 0;
407 }
408}
409
410void HAL_Pulse(HAL_DigitalHandle dioPortHandle, double pulseLength,
411 int32_t* status) {
412 auto port = digitalChannelHandles->Get(dioPortHandle, HAL_HandleEnum::DIO);
413 if (port == nullptr) {
414 *status = HAL_HANDLE_ERROR;
415 return;
416 }
417 tDIO::tPulse pulse;
418
419 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
420 pulse.SPIPort = 1u << remapSPIChannel(port->channel);
421 } else if (port->channel < kNumDigitalHeaders) {
422 pulse.Headers = 1u << port->channel;
423 } else {
424 pulse.MXP = 1u << remapMXPChannel(port->channel);
425 }
426
427 digitalSystem->writePulseLength(
428 static_cast<uint16_t>(1.0e9 * pulseLength /
429 (pwmSystem->readLoopTiming(status) * 25)),
430 status);
431 digitalSystem->writePulse(pulse, status);
432}
433
434HAL_Bool HAL_IsPulsing(HAL_DigitalHandle dioPortHandle, int32_t* status) {
435 auto port = digitalChannelHandles->Get(dioPortHandle, HAL_HandleEnum::DIO);
436 if (port == nullptr) {
437 *status = HAL_HANDLE_ERROR;
438 return false;
439 }
440 tDIO::tPulse pulseRegister = digitalSystem->readPulse(status);
441
442 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
443 return (pulseRegister.SPIPort & (1 << remapSPIChannel(port->channel))) != 0;
444 } else if (port->channel < kNumDigitalHeaders) {
445 return (pulseRegister.Headers & (1 << port->channel)) != 0;
446 } else {
447 return (pulseRegister.MXP & (1 << remapMXPChannel(port->channel))) != 0;
448 }
449}
450
451HAL_Bool HAL_IsAnyPulsing(int32_t* status) {
452 initializeDigital(status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700453 if (*status != 0) {
454 return false;
455 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800456 tDIO::tPulse pulseRegister = digitalSystem->readPulse(status);
457 return pulseRegister.Headers != 0 && pulseRegister.MXP != 0 &&
458 pulseRegister.SPIPort != 0;
459}
460
461void HAL_SetFilterSelect(HAL_DigitalHandle dioPortHandle, int32_t filterIndex,
462 int32_t* status) {
463 auto port = digitalChannelHandles->Get(dioPortHandle, HAL_HandleEnum::DIO);
464 if (port == nullptr) {
465 *status = HAL_HANDLE_ERROR;
466 return;
467 }
468
469 std::scoped_lock lock(digitalDIOMutex);
470 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
471 // Channels 10-15 are SPI channels, so subtract our MXP channels
472 digitalSystem->writeFilterSelectHdr(port->channel - kNumDigitalMXPChannels,
473 filterIndex, status);
474 } else if (port->channel < kNumDigitalHeaders) {
475 digitalSystem->writeFilterSelectHdr(port->channel, filterIndex, status);
476 } else {
477 digitalSystem->writeFilterSelectMXP(remapMXPChannel(port->channel),
478 filterIndex, status);
479 }
480}
481
482int32_t HAL_GetFilterSelect(HAL_DigitalHandle dioPortHandle, int32_t* status) {
483 auto port = digitalChannelHandles->Get(dioPortHandle, HAL_HandleEnum::DIO);
484 if (port == nullptr) {
485 *status = HAL_HANDLE_ERROR;
486 return 0;
487 }
488
489 std::scoped_lock lock(digitalDIOMutex);
490 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
491 // Channels 10-15 are SPI channels, so subtract our MXP channels
492 return digitalSystem->readFilterSelectHdr(
493 port->channel - kNumDigitalMXPChannels, status);
494 } else if (port->channel < kNumDigitalHeaders) {
495 return digitalSystem->readFilterSelectHdr(port->channel, status);
496 } else {
497 return digitalSystem->readFilterSelectMXP(remapMXPChannel(port->channel),
498 status);
499 }
500}
501
502void HAL_SetFilterPeriod(int32_t filterIndex, int64_t value, int32_t* status) {
503 initializeDigital(status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700504 if (*status != 0) {
505 return;
506 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800507 std::scoped_lock lock(digitalDIOMutex);
508 digitalSystem->writeFilterPeriodHdr(filterIndex, value, status);
509 if (*status == 0) {
510 digitalSystem->writeFilterPeriodMXP(filterIndex, value, status);
511 }
512}
513
514int64_t HAL_GetFilterPeriod(int32_t filterIndex, int32_t* status) {
515 initializeDigital(status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700516 if (*status != 0) {
517 return 0;
518 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800519 uint32_t hdrPeriod = 0;
520 uint32_t mxpPeriod = 0;
521 {
522 std::scoped_lock lock(digitalDIOMutex);
523 hdrPeriod = digitalSystem->readFilterPeriodHdr(filterIndex, status);
524 if (*status == 0) {
525 mxpPeriod = digitalSystem->readFilterPeriodMXP(filterIndex, status);
526 }
527 }
528 if (hdrPeriod != mxpPeriod) {
529 *status = NiFpga_Status_SoftwareFault;
530 return -1;
531 }
532 return hdrPeriod;
533}
534
535} // extern "C"