blob: cf01397a61ae551f9fba6b97b4ebecafd3d75f24 [file] [log] [blame]
Brian Silvermanf7f267a2017-02-04 16:16:08 -08001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2016-2017. 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/DIO.h"
9
10#include <cmath>
11
12#include "DigitalInternal.h"
13#include "HAL/handles/HandlesInternal.h"
14#include "HAL/handles/LimitedHandleResource.h"
15#include "PortsInternal.h"
16
17using namespace hal;
18
19// Create a mutex to protect changes to the digital output values
20static priority_recursive_mutex digitalDIOMutex;
21
22static LimitedHandleResource<HAL_DigitalPWMHandle, uint8_t,
23 kNumDigitalPWMOutputs, HAL_HandleEnum::DigitalPWM>
24 digitalPWMHandles;
25
26extern "C" {
27
28/**
29 * Create a new instance of a digital port.
30 */
31HAL_DigitalHandle HAL_InitializeDIOPort(HAL_PortHandle portHandle,
32 HAL_Bool input, int32_t* status) {
33 initializeDigital(status);
34
35 if (*status != 0) return HAL_kInvalidHandle;
36
37 int16_t channel = getPortHandleChannel(portHandle);
38 if (channel == InvalidHandleIndex || channel >= kNumDigitalChannels) {
39 *status = PARAMETER_OUT_OF_RANGE;
40 return HAL_kInvalidHandle;
41 }
42
43 auto handle =
44 digitalChannelHandles.Allocate(channel, HAL_HandleEnum::DIO, status);
45
46 if (*status != 0)
47 return HAL_kInvalidHandle; // failed to allocate. Pass error back.
48
49 auto port = digitalChannelHandles.Get(handle, HAL_HandleEnum::DIO);
50 if (port == nullptr) { // would only occur on thread issue.
51 *status = HAL_HANDLE_ERROR;
52 return HAL_kInvalidHandle;
53 }
54
55 port->channel = static_cast<uint8_t>(channel);
56
57 std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
58
59 tDIO::tOutputEnable outputEnable = digitalSystem->readOutputEnable(status);
60
61 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
62 if (!getPortHandleSPIEnable(portHandle)) {
63 // if this flag is not set, we actually want DIO.
64 uint32_t bitToSet = 1u << remapSPIChannel(port->channel);
65
66 uint16_t specialFunctions = spiSystem->readEnableDIO(status);
67 // Set the field to enable SPI DIO
68 spiSystem->writeEnableDIO(specialFunctions | bitToSet, status);
69
70 if (input) {
71 outputEnable.SPIPort =
72 outputEnable.SPIPort & (~bitToSet); // clear the field for read
73 } else {
74 outputEnable.SPIPort =
75 outputEnable.SPIPort | bitToSet; // set the bits for write
76 }
77 }
78 } else if (port->channel < kNumDigitalHeaders) {
79 uint32_t bitToSet = 1u << port->channel;
80 if (input) {
81 outputEnable.Headers =
82 outputEnable.Headers & (~bitToSet); // clear the bit for read
83 } else {
84 outputEnable.Headers =
85 outputEnable.Headers | bitToSet; // set the bit for write
86 }
87 } else {
88 uint32_t bitToSet = 1u << remapMXPChannel(port->channel);
89
90 uint16_t specialFunctions =
91 digitalSystem->readEnableMXPSpecialFunction(status);
92 digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToSet,
93 status);
94
95 if (input) {
96 outputEnable.MXP =
97 outputEnable.MXP & (~bitToSet); // clear the bit for read
98 } else {
99 outputEnable.MXP = outputEnable.MXP | bitToSet; // set the bit for write
100 }
101 }
102
103 digitalSystem->writeOutputEnable(outputEnable, status);
104
105 return handle;
106}
107
108HAL_Bool HAL_CheckDIOChannel(int32_t channel) {
109 return channel < kNumDigitalChannels && channel >= 0;
110}
111
112void HAL_FreeDIOPort(HAL_DigitalHandle dioPortHandle) {
113 auto port = digitalChannelHandles.Get(dioPortHandle, HAL_HandleEnum::DIO);
114 // no status, so no need to check for a proper free.
115 digitalChannelHandles.Free(dioPortHandle, HAL_HandleEnum::DIO);
116 if (port == nullptr) return;
117 int32_t status = 0;
118 std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
119 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
120 // Unset the SPI flag
121 int32_t bitToUnset = 1 << remapSPIChannel(port->channel);
122 uint16_t specialFunctions = spiSystem->readEnableDIO(&status);
123 spiSystem->writeEnableDIO(specialFunctions & ~bitToUnset, &status);
124 } else if (port->channel >= kNumDigitalHeaders) {
125 // Unset the MXP flag
126 uint32_t bitToUnset = 1u << remapMXPChannel(port->channel);
127
128 uint16_t specialFunctions =
129 digitalSystem->readEnableMXPSpecialFunction(&status);
130 digitalSystem->writeEnableMXPSpecialFunction(specialFunctions | bitToUnset,
131 &status);
132 }
133}
134
135/**
136 * Allocate a DO PWM Generator.
137 * Allocate PWM generators so that they are not accidentally reused.
138 *
139 * @return PWM Generator handle
140 */
141HAL_DigitalPWMHandle HAL_AllocateDigitalPWM(int32_t* status) {
142 auto handle = digitalPWMHandles.Allocate();
143 if (handle == HAL_kInvalidHandle) {
144 *status = NO_AVAILABLE_RESOURCES;
145 return HAL_kInvalidHandle;
146 }
147
148 auto id = digitalPWMHandles.Get(handle);
149 if (id == nullptr) { // would only occur on thread issue.
150 *status = HAL_HANDLE_ERROR;
151 return HAL_kInvalidHandle;
152 }
153 *id = static_cast<uint8_t>(getHandleIndex(handle));
154
155 return handle;
156}
157
158/**
159 * Free the resource associated with a DO PWM generator.
160 *
161 * @param pwmGenerator The pwmGen to free that was allocated with
162 * allocateDigitalPWM()
163 */
164void HAL_FreeDigitalPWM(HAL_DigitalPWMHandle pwmGenerator, int32_t* status) {
165 digitalPWMHandles.Free(pwmGenerator);
166}
167
168/**
169 * Change the frequency of the DO PWM generator.
170 *
171 * The valid range is from 0.6 Hz to 19 kHz. The frequency resolution is
172 * logarithmic.
173 *
174 * @param rate The frequency to output all digital output PWM signals.
175 */
176void HAL_SetDigitalPWMRate(double rate, int32_t* status) {
177 // Currently rounding in the log rate domain... heavy weight toward picking a
178 // higher freq.
179 // TODO: Round in the linear rate domain.
180 initializeDigital(status);
181 if (*status != 0) return;
182 uint8_t pwmPeriodPower = static_cast<uint8_t>(
183 std::log(1.0 / (pwmSystem->readLoopTiming(status) * 0.25E-6 * rate)) /
184 std::log(2.0) +
185 0.5);
186 digitalSystem->writePWMPeriodPower(pwmPeriodPower, status);
187}
188
189/**
190 * Configure the duty-cycle of the PWM generator
191 *
192 * @param pwmGenerator The generator index reserved by allocateDigitalPWM()
193 * @param dutyCycle The percent duty cycle to output [0..1].
194 */
195void HAL_SetDigitalPWMDutyCycle(HAL_DigitalPWMHandle pwmGenerator,
196 double dutyCycle, int32_t* status) {
197 auto port = digitalPWMHandles.Get(pwmGenerator);
198 if (port == nullptr) {
199 *status = HAL_HANDLE_ERROR;
200 return;
201 }
202 int32_t id = *port;
203 if (dutyCycle > 1.0) dutyCycle = 1.0;
204 if (dutyCycle < 0.0) dutyCycle = 0.0;
205 double rawDutyCycle = 256.0 * dutyCycle;
206 if (rawDutyCycle > 255.5) rawDutyCycle = 255.5;
207 {
208 std::lock_guard<priority_recursive_mutex> sync(digitalPwmMutex);
209 uint16_t pwmPeriodPower = digitalSystem->readPWMPeriodPower(status);
210 if (pwmPeriodPower < 4) {
211 // The resolution of the duty cycle drops close to the highest
212 // frequencies.
213 rawDutyCycle = rawDutyCycle / std::pow(2.0, 4 - pwmPeriodPower);
214 }
215 if (id < 4)
216 digitalSystem->writePWMDutyCycleA(id, static_cast<uint8_t>(rawDutyCycle),
217 status);
218 else
219 digitalSystem->writePWMDutyCycleB(
220 id - 4, static_cast<uint8_t>(rawDutyCycle), status);
221 }
222}
223
224/**
225 * Configure which DO channel the PWM signal is output on
226 *
227 * @param pwmGenerator The generator index reserved by allocateDigitalPWM()
228 * @param channel The Digital Output channel to output on
229 */
230void HAL_SetDigitalPWMOutputChannel(HAL_DigitalPWMHandle pwmGenerator,
231 int32_t channel, int32_t* status) {
232 auto port = digitalPWMHandles.Get(pwmGenerator);
233 if (port == nullptr) {
234 *status = HAL_HANDLE_ERROR;
235 return;
236 }
237 int32_t id = *port;
238 if (channel >= kNumDigitalHeaders &&
239 channel <
240 kNumDigitalHeaders + kNumDigitalMXPChannels) { // If it is on the MXP
241 /* Then to write as a digital PWM channel an offset is needed to write on
242 * the correct channel
243 */
244 channel += kMXPDigitalPWMOffset;
245 }
246 digitalSystem->writePWMOutputSelect(id, channel, status);
247}
248
249/**
250 * Write a digital I/O bit to the FPGA.
251 * Set a single value on a digital I/O channel.
252 *
253 * @param channel The Digital I/O channel
254 * @param value The state to set the digital channel (if it is configured as an
255 * output)
256 */
257void HAL_SetDIO(HAL_DigitalHandle dioPortHandle, HAL_Bool value,
258 int32_t* status) {
259 auto port = digitalChannelHandles.Get(dioPortHandle, HAL_HandleEnum::DIO);
260 if (port == nullptr) {
261 *status = HAL_HANDLE_ERROR;
262 return;
263 }
264 if (value != 0 && value != 1) {
265 if (value != 0) value = 1;
266 }
267 {
268 std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
269 tDIO::tDO currentDIO = digitalSystem->readDO(status);
270
271 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
272 if (value == 0) {
273 currentDIO.SPIPort =
274 currentDIO.SPIPort & ~(1u << remapSPIChannel(port->channel));
275 } else if (value == 1) {
276 currentDIO.SPIPort =
277 currentDIO.SPIPort | (1u << remapSPIChannel(port->channel));
278 }
279 } else if (port->channel < kNumDigitalHeaders) {
280 if (value == 0) {
281 currentDIO.Headers = currentDIO.Headers & ~(1u << port->channel);
282 } else if (value == 1) {
283 currentDIO.Headers = currentDIO.Headers | (1u << port->channel);
284 }
285 } else {
286 if (value == 0) {
287 currentDIO.MXP =
288 currentDIO.MXP & ~(1u << remapMXPChannel(port->channel));
289 } else if (value == 1) {
290 currentDIO.MXP =
291 currentDIO.MXP | (1u << remapMXPChannel(port->channel));
292 }
293 }
294 digitalSystem->writeDO(currentDIO, status);
295 }
296}
297
298/**
299 * Read a digital I/O bit from the FPGA.
300 * Get a single value from a digital I/O channel.
301 *
302 * @param channel The digital I/O channel
303 * @return The state of the specified channel
304 */
305HAL_Bool HAL_GetDIO(HAL_DigitalHandle dioPortHandle, int32_t* status) {
306 auto port = digitalChannelHandles.Get(dioPortHandle, HAL_HandleEnum::DIO);
307 if (port == nullptr) {
308 *status = HAL_HANDLE_ERROR;
309 return false;
310 }
311 tDIO::tDI currentDIO = digitalSystem->readDI(status);
312 // Shift 00000001 over channel-1 places.
313 // AND it against the currentDIO
314 // if it == 0, then return false
315 // else return true
316
317 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
318 return ((currentDIO.SPIPort >> remapSPIChannel(port->channel)) & 1) != 0;
319 } else if (port->channel < kNumDigitalHeaders) {
320 return ((currentDIO.Headers >> port->channel) & 1) != 0;
321 } else {
322 return ((currentDIO.MXP >> remapMXPChannel(port->channel)) & 1) != 0;
323 }
324}
325
326/**
327 * Read the direction of a the Digital I/O lines
328 * A 1 bit means output and a 0 bit means input.
329 *
330 * @param channel The digital I/O channel
331 * @return The direction of the specified channel
332 */
333HAL_Bool HAL_GetDIODirection(HAL_DigitalHandle dioPortHandle, int32_t* status) {
334 auto port = digitalChannelHandles.Get(dioPortHandle, HAL_HandleEnum::DIO);
335 if (port == nullptr) {
336 *status = HAL_HANDLE_ERROR;
337 return false;
338 }
339 tDIO::tOutputEnable currentOutputEnable =
340 digitalSystem->readOutputEnable(status);
341 // Shift 00000001 over port->channel-1 places.
342 // AND it against the currentOutputEnable
343 // if it == 0, then return false
344 // else return true
345
346 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
347 return ((currentOutputEnable.SPIPort >> remapSPIChannel(port->channel)) &
348 1) != 0;
349 } else if (port->channel < kNumDigitalHeaders) {
350 return ((currentOutputEnable.Headers >> port->channel) & 1) != 0;
351 } else {
352 return ((currentOutputEnable.MXP >> remapMXPChannel(port->channel)) & 1) !=
353 0;
354 }
355}
356
357/**
358 * Generate a single pulse.
359 * Write a pulse to the specified digital output channel. There can only be a
360 * single pulse going at any time.
361 *
362 * @param channel The Digital Output channel that the pulse should be output on
363 * @param pulseLength The active length of the pulse (in seconds)
364 */
365void HAL_Pulse(HAL_DigitalHandle dioPortHandle, double pulseLength,
366 int32_t* status) {
367 auto port = digitalChannelHandles.Get(dioPortHandle, HAL_HandleEnum::DIO);
368 if (port == nullptr) {
369 *status = HAL_HANDLE_ERROR;
370 return;
371 }
372 tDIO::tPulse pulse;
373
374 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
375 pulse.SPIPort = 1u << remapSPIChannel(port->channel);
376 } else if (port->channel < kNumDigitalHeaders) {
377 pulse.Headers = 1u << port->channel;
378 } else {
379 pulse.MXP = 1u << remapMXPChannel(port->channel);
380 }
381
382 digitalSystem->writePulseLength(
383 static_cast<uint8_t>(1.0e9 * pulseLength /
384 (pwmSystem->readLoopTiming(status) * 25)),
385 status);
386 digitalSystem->writePulse(pulse, status);
387}
388
389/**
390 * Check a DIO line to see if it is currently generating a pulse.
391 *
392 * @return A pulse is in progress
393 */
394HAL_Bool HAL_IsPulsing(HAL_DigitalHandle dioPortHandle, int32_t* status) {
395 auto port = digitalChannelHandles.Get(dioPortHandle, HAL_HandleEnum::DIO);
396 if (port == nullptr) {
397 *status = HAL_HANDLE_ERROR;
398 return false;
399 }
400 tDIO::tPulse pulseRegister = digitalSystem->readPulse(status);
401
402 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
403 return (pulseRegister.SPIPort & (1 << remapSPIChannel(port->channel))) != 0;
404 } else if (port->channel < kNumDigitalHeaders) {
405 return (pulseRegister.Headers & (1 << port->channel)) != 0;
406 } else {
407 return (pulseRegister.MXP & (1 << remapMXPChannel(port->channel))) != 0;
408 }
409}
410
411/**
412 * Check if any DIO line is currently generating a pulse.
413 *
414 * @return A pulse on some line is in progress
415 */
416HAL_Bool HAL_IsAnyPulsing(int32_t* status) {
417 initializeDigital(status);
418 if (*status != 0) return false;
419 tDIO::tPulse pulseRegister = digitalSystem->readPulse(status);
420 return pulseRegister.Headers != 0 && pulseRegister.MXP != 0 &&
421 pulseRegister.SPIPort != 0;
422}
423
424/**
425 * Write the filter index from the FPGA.
426 * Set the filter index used to filter out short pulses.
427 *
428 * @param dioPortHandle Handle to the digital I/O channel
429 * @param filterIndex The filter index. Must be in the range 0 - 3, where 0
430 * means "none" and 1 - 3 means filter # filterIndex - 1.
431 */
432void HAL_SetFilterSelect(HAL_DigitalHandle dioPortHandle, int32_t filterIndex,
433 int32_t* status) {
434 auto port = digitalChannelHandles.Get(dioPortHandle, HAL_HandleEnum::DIO);
435 if (port == nullptr) {
436 *status = HAL_HANDLE_ERROR;
437 return;
438 }
439
440 std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
441 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
442 // Channels 10-15 are SPI channels, so subtract our MXP channels
443 digitalSystem->writeFilterSelectHdr(port->channel - kNumDigitalMXPChannels,
444 filterIndex, status);
445 } else if (port->channel < kNumDigitalHeaders) {
446 digitalSystem->writeFilterSelectHdr(port->channel, filterIndex, status);
447 } else {
448 digitalSystem->writeFilterSelectMXP(remapMXPChannel(port->channel),
449 filterIndex, status);
450 }
451}
452
453/**
454 * Read the filter index from the FPGA.
455 * Get the filter index used to filter out short pulses.
456 *
457 * @param dioPortHandle Handle to the digital I/O channel
458 * @return filterIndex The filter index. Must be in the range 0 - 3,
459 * where 0 means "none" and 1 - 3 means filter # filterIndex - 1.
460 */
461int32_t HAL_GetFilterSelect(HAL_DigitalHandle dioPortHandle, int32_t* status) {
462 auto port = digitalChannelHandles.Get(dioPortHandle, HAL_HandleEnum::DIO);
463 if (port == nullptr) {
464 *status = HAL_HANDLE_ERROR;
465 return 0;
466 }
467
468 std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
469 if (port->channel >= kNumDigitalHeaders + kNumDigitalMXPChannels) {
470 // Channels 10-15 are SPI channels, so subtract our MXP channels
471 return digitalSystem->readFilterSelectHdr(
472 port->channel - kNumDigitalMXPChannels, status);
473 } else if (port->channel < kNumDigitalHeaders) {
474 return digitalSystem->readFilterSelectHdr(port->channel, status);
475 } else {
476 return digitalSystem->readFilterSelectMXP(remapMXPChannel(port->channel),
477 status);
478 }
479}
480
481/**
482 * Set the filter period for the specified filter index.
483 *
484 * Set the filter period in FPGA cycles. Even though there are 2 different
485 * filter index domains (MXP vs HDR), ignore that distinction for now since it
486 * compilicates the interface. That can be changed later.
487 *
488 * @param filterIndex The filter index, 0 - 2.
489 * @param value The number of cycles that the signal must not transition to be
490 * counted as a transition.
491 */
492void HAL_SetFilterPeriod(int32_t filterIndex, int64_t value, int32_t* status) {
493 initializeDigital(status);
494 if (*status != 0) return;
495 std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
496 digitalSystem->writeFilterPeriodHdr(filterIndex, value, status);
497 if (*status == 0) {
498 digitalSystem->writeFilterPeriodMXP(filterIndex, value, status);
499 }
500}
501
502/**
503 * Get the filter period for the specified filter index.
504 *
505 * Get the filter period in FPGA cycles. Even though there are 2 different
506 * filter index domains (MXP vs HDR), ignore that distinction for now since it
507 * compilicates the interface. Set status to NiFpga_Status_SoftwareFault if the
508 * filter values miss-match.
509 *
510 * @param filterIndex The filter index, 0 - 2.
511 * @param value The number of cycles that the signal must not transition to be
512 * counted as a transition.
513 */
514int64_t HAL_GetFilterPeriod(int32_t filterIndex, int32_t* status) {
515 initializeDigital(status);
516 if (*status != 0) return 0;
517 uint32_t hdrPeriod = 0;
518 uint32_t mxpPeriod = 0;
519 {
520 std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex);
521 hdrPeriod = digitalSystem->readFilterPeriodHdr(filterIndex, status);
522 if (*status == 0) {
523 mxpPeriod = digitalSystem->readFilterPeriodMXP(filterIndex, status);
524 }
525 }
526 if (hdrPeriod != mxpPeriod) {
527 *status = NiFpga_Status_SoftwareFault;
528 return -1;
529 }
530 return hdrPeriod;
531}
532}