Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 1 | /*----------------------------------------------------------------------------*/ |
| 2 | /* Copyright (c) FIRST 2016. 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 | /*----------------------------------------------------------------------------*/ |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 7 | |
| 8 | #include "HAL/Digital.hpp" |
| 9 | |
| 10 | #include "HAL/Port.h" |
| 11 | #include "HAL/HAL.hpp" |
| 12 | #include "ChipObject.h" |
| 13 | #include "HAL/cpp/Resource.hpp" |
| 14 | #include "HAL/cpp/priority_mutex.h" |
| 15 | #include "FRC_NetworkCommunication/LoadOut.h" |
| 16 | #include <stdio.h> |
| 17 | #include <math.h> |
| 18 | #include <mutex> |
| 19 | #include "i2clib/i2c-lib.h" |
| 20 | #include "spilib/spi-lib.h" |
| 21 | |
| 22 | static_assert(sizeof(uint32_t) <= sizeof(void *), "This file shoves uint32_ts into pointers."); |
| 23 | |
| 24 | static const uint32_t kExpectedLoopTiming = 40; |
| 25 | static const uint32_t kDigitalPins = 26; |
| 26 | static const uint32_t kPwmPins = 20; |
| 27 | static const uint32_t kRelayPins = 8; |
| 28 | static const uint32_t kNumHeaders = 10; // Number of non-MXP pins |
| 29 | |
| 30 | /** |
| 31 | * kDefaultPwmPeriod is in ms |
| 32 | * |
| 33 | * - 20ms periods (50 Hz) are the "safest" setting in that this works for all devices |
| 34 | * - 20ms periods seem to be desirable for Vex Motors |
| 35 | * - 20ms periods are the specified period for HS-322HD servos, but work reliably down |
| 36 | * to 10.0 ms; starting at about 8.5ms, the servo sometimes hums and get hot; |
| 37 | * by 5.0ms the hum is nearly continuous |
| 38 | * - 10ms periods work well for Victor 884 |
| 39 | * - 5ms periods allows higher update rates for Luminary Micro Jaguar speed controllers. |
| 40 | * Due to the shipping firmware on the Jaguar, we can't run the update period less |
| 41 | * than 5.05 ms. |
| 42 | * |
| 43 | * kDefaultPwmPeriod is the 1x period (5.05 ms). In hardware, the period scaling is implemented as an |
| 44 | * output squelch to get longer periods for old devices. |
| 45 | */ |
| 46 | static const float kDefaultPwmPeriod = 5.05; |
| 47 | /** |
| 48 | * kDefaultPwmCenter is the PWM range center in ms |
| 49 | */ |
| 50 | static const float kDefaultPwmCenter = 1.5; |
| 51 | /** |
| 52 | * kDefaultPWMStepsDown is the number of PWM steps below the centerpoint |
| 53 | */ |
| 54 | static const int32_t kDefaultPwmStepsDown = 1000; |
| 55 | static const int32_t kPwmDisabled = 0; |
| 56 | |
| 57 | struct DigitalPort { |
| 58 | Port port; |
| 59 | uint32_t PWMGeneratorID; |
| 60 | }; |
| 61 | |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 62 | // Create a mutex to protect changes to the digital output values |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 63 | static priority_recursive_mutex digitalDIOMutex; |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 64 | // Create a mutex to protect changes to the relay values |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 65 | static priority_recursive_mutex digitalRelayMutex; |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 66 | // Create a mutex to protect changes to the DO PWM config |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 67 | static priority_recursive_mutex digitalPwmMutex; |
| 68 | static priority_recursive_mutex digitalI2COnBoardMutex; |
| 69 | static priority_recursive_mutex digitalI2CMXPMutex; |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 70 | |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 71 | static tDIO* digitalSystem = NULL; |
| 72 | static tRelay* relaySystem = NULL; |
| 73 | static tPWM* pwmSystem = NULL; |
| 74 | static hal::Resource *DIOChannels = NULL; |
| 75 | static hal::Resource *DO_PWMGenerators = NULL; |
| 76 | static hal::Resource *PWMChannels = NULL; |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 77 | |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 78 | static bool digitalSystemsInitialized = false; |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 79 | |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 80 | static uint8_t i2COnboardObjCount = 0; |
| 81 | static uint8_t i2CMXPObjCount = 0; |
| 82 | static uint8_t i2COnBoardHandle = 0; |
| 83 | static uint8_t i2CMXPHandle = 0; |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 84 | |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 85 | static int32_t m_spiCS0Handle = 0; |
| 86 | static int32_t m_spiCS1Handle = 0; |
| 87 | static int32_t m_spiCS2Handle = 0; |
| 88 | static int32_t m_spiCS3Handle = 0; |
| 89 | static int32_t m_spiMXPHandle = 0; |
| 90 | static priority_recursive_mutex spiOnboardSemaphore; |
| 91 | static priority_recursive_mutex spiMXPSemaphore; |
| 92 | static tSPI *spiSystem; |
| 93 | |
| 94 | extern "C" { |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 95 | |
| 96 | struct SPIAccumulator { |
| 97 | void* notifier = nullptr; |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 98 | uint64_t triggerTime; |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 99 | uint32_t period; |
| 100 | |
| 101 | int64_t value = 0; |
| 102 | uint32_t count = 0; |
| 103 | int32_t last_value = 0; |
| 104 | |
| 105 | int32_t center = 0; |
| 106 | int32_t deadband = 0; |
| 107 | |
| 108 | uint8_t cmd[4]; // command to send (up to 4 bytes) |
| 109 | uint32_t valid_mask; |
| 110 | uint32_t valid_value; |
| 111 | int32_t data_max; // one more than max data value |
| 112 | int32_t data_msb_mask; // data field MSB mask (for signed) |
| 113 | uint8_t data_shift; // data field shift right amount, in bits |
| 114 | uint8_t xfer_size; // SPI transfer size, in bytes (up to 4) |
| 115 | uint8_t port; |
| 116 | bool is_signed; // is data field signed? |
| 117 | bool big_endian; // is response big endian? |
| 118 | }; |
| 119 | SPIAccumulator* spiAccumulators[5] = {nullptr, nullptr, nullptr, nullptr, nullptr}; |
| 120 | |
| 121 | /** |
| 122 | * Initialize the digital system. |
| 123 | */ |
| 124 | void initializeDigital(int32_t *status) { |
| 125 | if (digitalSystemsInitialized) return; |
| 126 | |
| 127 | hal::Resource::CreateResourceObject(&DIOChannels, tDIO::kNumSystems * kDigitalPins); |
| 128 | hal::Resource::CreateResourceObject(&DO_PWMGenerators, tDIO::kNumPWMDutyCycleAElements + tDIO::kNumPWMDutyCycleBElements); |
| 129 | hal::Resource::CreateResourceObject(&PWMChannels, tPWM::kNumSystems * kPwmPins); |
| 130 | digitalSystem = tDIO::create(status); |
| 131 | |
| 132 | // Relay Setup |
| 133 | relaySystem = tRelay::create(status); |
| 134 | |
| 135 | // Turn off all relay outputs. |
| 136 | relaySystem->writeValue_Forward(0, status); |
| 137 | relaySystem->writeValue_Reverse(0, status); |
| 138 | |
| 139 | // PWM Setup |
| 140 | pwmSystem = tPWM::create(status); |
| 141 | |
| 142 | // Make sure that the 9403 IONode has had a chance to initialize before continuing. |
| 143 | while(pwmSystem->readLoopTiming(status) == 0) delayTicks(1); |
| 144 | |
| 145 | if (pwmSystem->readLoopTiming(status) != kExpectedLoopTiming) { |
| 146 | // TODO: char err[128]; |
| 147 | // TODO: sprintf(err, "DIO LoopTiming: %d, expecting: %lu\n", digitalModules[port->module-1]->readLoopTiming(status), kExpectedLoopTiming); |
| 148 | *status = LOOP_TIMING_ERROR; // NOTE: Doesn't display the error |
| 149 | } |
| 150 | |
| 151 | //Calculate the length, in ms, of one DIO loop |
| 152 | double loopTime = pwmSystem->readLoopTiming(status)/(kSystemClockTicksPerMicrosecond*1e3); |
| 153 | |
| 154 | pwmSystem->writeConfig_Period((uint16_t) (kDefaultPwmPeriod/loopTime + .5), status); |
| 155 | uint16_t minHigh = (uint16_t) ((kDefaultPwmCenter-kDefaultPwmStepsDown*loopTime)/loopTime + .5); |
| 156 | pwmSystem->writeConfig_MinHigh(minHigh, status); |
| 157 | // printf("MinHigh: %d\n", minHigh); |
| 158 | // Ensure that PWM output values are set to OFF |
| 159 | for (uint32_t pwm_index = 0; pwm_index < kPwmPins; pwm_index++) { |
| 160 | // Initialize port structure |
| 161 | DigitalPort digital_port; |
| 162 | digital_port.port.pin = pwm_index; |
| 163 | |
| 164 | setPWM(&digital_port, kPwmDisabled, status); |
| 165 | setPWMPeriodScale(&digital_port, 3, status); // Set all to 4x by default. |
| 166 | } |
| 167 | |
| 168 | digitalSystemsInitialized = true; |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * Create a new instance of a digital port. |
| 173 | */ |
| 174 | void* initializeDigitalPort(void* port_pointer, int32_t *status) { |
| 175 | initializeDigital(status); |
| 176 | Port* port = (Port*) port_pointer; |
| 177 | |
| 178 | // Initialize port structure |
| 179 | DigitalPort* digital_port = new DigitalPort(); |
| 180 | digital_port->port = *port; |
| 181 | |
| 182 | return digital_port; |
| 183 | } |
| 184 | |
| 185 | void freeDigitalPort(void* digital_port_pointer) { |
| 186 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 187 | delete port; |
| 188 | } |
| 189 | |
| 190 | bool checkPWMChannel(void* digital_port_pointer) { |
| 191 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 192 | return port->port.pin < kPwmPins; |
| 193 | } |
| 194 | |
| 195 | bool checkRelayChannel(void* digital_port_pointer) { |
| 196 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 197 | return port->port.pin < kRelayPins; |
| 198 | } |
| 199 | |
| 200 | /** |
| 201 | * Check a port to make sure that it is not NULL and is a valid PWM port. |
| 202 | * |
| 203 | * Sets the status to contain the appropriate error. |
| 204 | * |
| 205 | * @return true if the port passed validation. |
| 206 | */ |
| 207 | static bool verifyPWMChannel(DigitalPort *port, int32_t *status) { |
| 208 | if (port == NULL) { |
| 209 | *status = NULL_PARAMETER; |
| 210 | return false; |
| 211 | } else if (!checkPWMChannel(port)) { |
| 212 | *status = PARAMETER_OUT_OF_RANGE; |
| 213 | return false; |
| 214 | } else { |
| 215 | return true; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Check a port to make sure that it is not NULL and is a valid Relay port. |
| 221 | * |
| 222 | * Sets the status to contain the appropriate error. |
| 223 | * |
| 224 | * @return true if the port passed validation. |
| 225 | */ |
| 226 | static bool verifyRelayChannel(DigitalPort *port, int32_t *status) { |
| 227 | if (port == NULL) { |
| 228 | *status = NULL_PARAMETER; |
| 229 | return false; |
| 230 | } else if (!checkRelayChannel(port)) { |
| 231 | *status = PARAMETER_OUT_OF_RANGE; |
| 232 | return false; |
| 233 | } else { |
| 234 | return true; |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | /** |
| 239 | * Map DIO pin numbers from their physical number (10 to 26) to their position |
| 240 | * in the bit field. |
| 241 | */ |
| 242 | uint32_t remapMXPChannel(uint32_t pin) { |
| 243 | return pin - 10; |
| 244 | } |
| 245 | |
| 246 | uint32_t remapMXPPWMChannel(uint32_t pin) { |
| 247 | if(pin < 14) { |
| 248 | return pin - 10; //first block of 4 pwms (MXP 0-3) |
| 249 | } else { |
| 250 | return pin - 6; //block of PWMs after SPI |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | /** |
| 255 | * Set a PWM channel to the desired value. The values range from 0 to 255 and the period is controlled |
| 256 | * by the PWM Period and MinHigh registers. |
| 257 | * |
| 258 | * @param channel The PWM channel to set. |
| 259 | * @param value The PWM value to set. |
| 260 | */ |
| 261 | void setPWM(void* digital_port_pointer, unsigned short value, int32_t *status) { |
| 262 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 263 | if (!verifyPWMChannel(port, status)) { return; } |
| 264 | |
| 265 | if(port->port.pin < tPWM::kNumHdrRegisters) { |
| 266 | pwmSystem->writeHdr(port->port.pin, value, status); |
| 267 | } else { |
| 268 | pwmSystem->writeMXP(port->port.pin - tPWM::kNumHdrRegisters, value, status); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Get a value from a PWM channel. The values range from 0 to 255. |
| 274 | * |
| 275 | * @param channel The PWM channel to read from. |
| 276 | * @return The raw PWM value. |
| 277 | */ |
| 278 | unsigned short getPWM(void* digital_port_pointer, int32_t *status) { |
| 279 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 280 | if (!verifyPWMChannel(port, status)) { return 0; } |
| 281 | |
| 282 | if(port->port.pin < tPWM::kNumHdrRegisters) { |
| 283 | return pwmSystem->readHdr(port->port.pin, status); |
| 284 | } else { |
| 285 | return pwmSystem->readMXP(port->port.pin - tPWM::kNumHdrRegisters, status); |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | void latchPWMZero(void* digital_port_pointer, int32_t *status) { |
| 290 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 291 | if (!verifyPWMChannel(port, status)) { return; } |
| 292 | |
| 293 | pwmSystem->writeZeroLatch(port->port.pin, true, status); |
| 294 | pwmSystem->writeZeroLatch(port->port.pin, false, status); |
| 295 | } |
| 296 | |
| 297 | /** |
| 298 | * Set how how often the PWM signal is squelched, thus scaling the period. |
| 299 | * |
| 300 | * @param channel The PWM channel to configure. |
| 301 | * @param squelchMask The 2-bit mask of outputs to squelch. |
| 302 | */ |
| 303 | void setPWMPeriodScale(void* digital_port_pointer, uint32_t squelchMask, int32_t *status) { |
| 304 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 305 | if (!verifyPWMChannel(port, status)) { return; } |
| 306 | |
| 307 | if(port->port.pin < tPWM::kNumPeriodScaleHdrElements) { |
| 308 | pwmSystem->writePeriodScaleHdr(port->port.pin, squelchMask, status); |
| 309 | } else { |
| 310 | pwmSystem->writePeriodScaleMXP(port->port.pin - tPWM::kNumPeriodScaleHdrElements, squelchMask, status); |
| 311 | } |
| 312 | } |
| 313 | |
| 314 | /** |
| 315 | * Allocate a DO PWM Generator. |
| 316 | * Allocate PWM generators so that they are not accidentally reused. |
| 317 | * |
| 318 | * @return PWM Generator refnum |
| 319 | */ |
| 320 | void* allocatePWM(int32_t *status) { |
| 321 | return (void*)DO_PWMGenerators->Allocate("DO_PWM"); |
| 322 | } |
| 323 | |
| 324 | /** |
| 325 | * Free the resource associated with a DO PWM generator. |
| 326 | * |
| 327 | * @param pwmGenerator The pwmGen to free that was allocated with AllocateDO_PWM() |
| 328 | */ |
| 329 | void freePWM(void* pwmGenerator, int32_t *status) { |
| 330 | uint32_t id = (uint32_t) pwmGenerator; |
| 331 | if (id == ~0ul) return; |
| 332 | DO_PWMGenerators->Free(id); |
| 333 | } |
| 334 | |
| 335 | /** |
| 336 | * Change the frequency of the DO PWM generator. |
| 337 | * |
| 338 | * The valid range is from 0.6 Hz to 19 kHz. The frequency resolution is logarithmic. |
| 339 | * |
| 340 | * @param rate The frequency to output all digital output PWM signals. |
| 341 | */ |
| 342 | void setPWMRate(double rate, int32_t *status) { |
| 343 | // Currently rounding in the log rate domain... heavy weight toward picking a higher freq. |
| 344 | // TODO: Round in the linear rate domain. |
| 345 | uint8_t pwmPeriodPower = (uint8_t)(log(1.0 / (pwmSystem->readLoopTiming(status) * 0.25E-6 * rate))/log(2.0) + 0.5); |
| 346 | digitalSystem->writePWMPeriodPower(pwmPeriodPower, status); |
| 347 | } |
| 348 | |
| 349 | |
| 350 | /** |
| 351 | * Configure the duty-cycle of the PWM generator |
| 352 | * |
| 353 | * @param pwmGenerator The generator index reserved by AllocateDO_PWM() |
| 354 | * @param dutyCycle The percent duty cycle to output [0..1]. |
| 355 | */ |
| 356 | void setPWMDutyCycle(void* pwmGenerator, double dutyCycle, int32_t *status) { |
| 357 | uint32_t id = (uint32_t) pwmGenerator; |
| 358 | if (id == ~0ul) return; |
| 359 | if (dutyCycle > 1.0) dutyCycle = 1.0; |
| 360 | if (dutyCycle < 0.0) dutyCycle = 0.0; |
| 361 | float rawDutyCycle = 256.0 * dutyCycle; |
| 362 | if (rawDutyCycle > 255.5) rawDutyCycle = 255.5; |
| 363 | { |
| 364 | std::lock_guard<priority_recursive_mutex> sync(digitalPwmMutex); |
| 365 | uint8_t pwmPeriodPower = digitalSystem->readPWMPeriodPower(status); |
| 366 | if (pwmPeriodPower < 4) { |
| 367 | // The resolution of the duty cycle drops close to the highest frequencies. |
| 368 | rawDutyCycle = rawDutyCycle / pow(2.0, 4 - pwmPeriodPower); |
| 369 | } |
| 370 | if(id < 4) |
| 371 | digitalSystem->writePWMDutyCycleA(id, (uint8_t)rawDutyCycle, status); |
| 372 | else |
| 373 | digitalSystem->writePWMDutyCycleB(id - 4, (uint8_t)rawDutyCycle, status); |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | /** |
| 378 | * Configure which DO channel the PWM signal is output on |
| 379 | * |
| 380 | * @param pwmGenerator The generator index reserved by AllocateDO_PWM() |
| 381 | * @param channel The Digital Output channel to output on |
| 382 | */ |
| 383 | void setPWMOutputChannel(void* pwmGenerator, uint32_t pin, int32_t *status) { |
| 384 | uint32_t id = (uint32_t) pwmGenerator; |
| 385 | if (id > 5) return; |
| 386 | digitalSystem->writePWMOutputSelect(id, pin, status); |
| 387 | } |
| 388 | |
| 389 | /** |
| 390 | * Set the state of a relay. |
| 391 | * Set the state of a relay output to be forward. Relays have two outputs and each is |
| 392 | * independently set to 0v or 12v. |
| 393 | */ |
| 394 | void setRelayForward(void* digital_port_pointer, bool on, int32_t *status) { |
| 395 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 396 | if (!verifyRelayChannel(port, status)) { |
| 397 | return; |
| 398 | } |
| 399 | |
| 400 | { |
| 401 | std::lock_guard<priority_recursive_mutex> sync(digitalRelayMutex); |
| 402 | uint8_t forwardRelays = relaySystem->readValue_Forward(status); |
| 403 | if (on) |
| 404 | forwardRelays |= 1 << port->port.pin; |
| 405 | else |
| 406 | forwardRelays &= ~(1 << port->port.pin); |
| 407 | relaySystem->writeValue_Forward(forwardRelays, status); |
| 408 | } |
| 409 | } |
| 410 | |
| 411 | /** |
| 412 | * Set the state of a relay. |
| 413 | * Set the state of a relay output to be reverse. Relays have two outputs and each is |
| 414 | * independently set to 0v or 12v. |
| 415 | */ |
| 416 | void setRelayReverse(void* digital_port_pointer, bool on, int32_t *status) { |
| 417 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 418 | if (!verifyRelayChannel(port, status)) { |
| 419 | return; |
| 420 | } |
| 421 | |
| 422 | { |
| 423 | std::lock_guard<priority_recursive_mutex> sync(digitalRelayMutex); |
| 424 | uint8_t reverseRelays = relaySystem->readValue_Reverse(status); |
| 425 | if (on) |
| 426 | reverseRelays |= 1 << port->port.pin; |
| 427 | else |
| 428 | reverseRelays &= ~(1 << port->port.pin); |
| 429 | relaySystem->writeValue_Reverse(reverseRelays, status); |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | /** |
| 434 | * Get the current state of the forward relay channel |
| 435 | */ |
| 436 | bool getRelayForward(void* digital_port_pointer, int32_t *status) { |
| 437 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 438 | if (!verifyRelayChannel(port, status)) { return false; } |
| 439 | |
| 440 | uint8_t forwardRelays = relaySystem->readValue_Forward(status); |
| 441 | return (forwardRelays & (1 << port->port.pin)) != 0; |
| 442 | } |
| 443 | |
| 444 | /** |
| 445 | * Get the current state of the reverse relay channel |
| 446 | */ |
| 447 | bool getRelayReverse(void* digital_port_pointer, int32_t *status) { |
| 448 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 449 | if (!verifyRelayChannel(port, status)) { return false; } |
| 450 | |
| 451 | uint8_t reverseRelays = relaySystem->readValue_Reverse(status); |
| 452 | return (reverseRelays & (1 << port->port.pin)) != 0; |
| 453 | } |
| 454 | |
| 455 | /** |
| 456 | * Allocate Digital I/O channels. |
| 457 | * Allocate channels so that they are not accidently reused. Also the direction is set at the |
| 458 | * time of the allocation. |
| 459 | * |
| 460 | * @param channel The Digital I/O channel |
| 461 | * @param input If true open as input; if false open as output |
| 462 | * @return Was successfully allocated |
| 463 | */ |
| 464 | bool allocateDIO(void* digital_port_pointer, bool input, int32_t *status) { |
| 465 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 466 | char buf[64]; |
| 467 | snprintf(buf, 64, "DIO %d", port->port.pin); |
| 468 | if (DIOChannels->Allocate(port->port.pin, buf) == ~0ul) { |
| 469 | *status = RESOURCE_IS_ALLOCATED; |
| 470 | return false; |
| 471 | } |
| 472 | |
| 473 | { |
| 474 | std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex); |
| 475 | |
| 476 | tDIO::tOutputEnable outputEnable = digitalSystem->readOutputEnable(status); |
| 477 | |
| 478 | if(port->port.pin < kNumHeaders) { |
| 479 | uint32_t bitToSet = 1 << port->port.pin; |
| 480 | if (input) { |
| 481 | outputEnable.Headers = outputEnable.Headers & (~bitToSet); // clear the bit for read |
| 482 | } else { |
| 483 | outputEnable.Headers = outputEnable.Headers | bitToSet; // set the bit for write |
| 484 | } |
| 485 | } else { |
| 486 | uint32_t bitToSet = 1 << remapMXPChannel(port->port.pin); |
| 487 | |
| 488 | // Disable special functions on this pin |
| 489 | short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status); |
| 490 | digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToSet, status); |
| 491 | |
| 492 | if (input) { |
| 493 | outputEnable.MXP = outputEnable.MXP & (~bitToSet); // clear the bit for read |
| 494 | } else { |
| 495 | outputEnable.MXP = outputEnable.MXP | bitToSet; // set the bit for write |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | digitalSystem->writeOutputEnable(outputEnable, status); |
| 500 | } |
| 501 | return true; |
| 502 | } |
| 503 | |
| 504 | bool allocatePWMChannel(void* digital_port_pointer, int32_t *status) { |
| 505 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 506 | if (!verifyPWMChannel(port, status)) { return false; } |
| 507 | |
| 508 | char buf[64]; |
| 509 | snprintf(buf, 64, "PWM %d", port->port.pin); |
| 510 | if (PWMChannels->Allocate(port->port.pin, buf) == ~0ul) { |
| 511 | *status = RESOURCE_IS_ALLOCATED; |
| 512 | return false; |
| 513 | } |
| 514 | |
| 515 | if (port->port.pin > tPWM::kNumHdrRegisters-1) { |
| 516 | snprintf(buf, 64, "PWM %d and DIO %d", port->port.pin, remapMXPPWMChannel(port->port.pin) + 10); |
| 517 | if (DIOChannels->Allocate(remapMXPPWMChannel(port->port.pin) + 10, buf) == ~0ul) return false; |
| 518 | uint32_t bitToSet = 1 << remapMXPPWMChannel(port->port.pin); |
| 519 | short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status); |
| 520 | digitalSystem->writeEnableMXPSpecialFunction(specialFunctions | bitToSet, status); |
| 521 | } |
| 522 | return true; |
| 523 | } |
| 524 | |
| 525 | void freePWMChannel(void* digital_port_pointer, int32_t *status) { |
| 526 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 527 | if (!port) return; |
| 528 | if (!verifyPWMChannel(port, status)) { return; } |
| 529 | |
| 530 | PWMChannels->Free(port->port.pin); |
| 531 | if(port->port.pin > tPWM::kNumHdrRegisters-1) { |
| 532 | DIOChannels->Free(remapMXPPWMChannel(port->port.pin) + 10); |
| 533 | uint32_t bitToUnset = 1 << remapMXPPWMChannel(port->port.pin); |
| 534 | short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status); |
| 535 | digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToUnset, status); |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | /** |
| 540 | * Free the resource associated with a digital I/O channel. |
| 541 | * |
| 542 | * @param channel The Digital I/O channel to free |
| 543 | */ |
| 544 | void freeDIO(void* digital_port_pointer, int32_t *status) { |
| 545 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 546 | if (!port) return; |
| 547 | DIOChannels->Free(port->port.pin); |
| 548 | } |
| 549 | |
| 550 | /** |
| 551 | * Write a digital I/O bit to the FPGA. |
| 552 | * Set a single value on a digital I/O channel. |
| 553 | * |
| 554 | * @param channel The Digital I/O channel |
| 555 | * @param value The state to set the digital channel (if it is configured as an output) |
| 556 | */ |
| 557 | void setDIO(void* digital_port_pointer, short value, int32_t *status) { |
| 558 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 559 | if (value != 0 && value != 1) { |
| 560 | if (value != 0) |
| 561 | value = 1; |
| 562 | } |
| 563 | { |
| 564 | std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex); |
| 565 | tDIO::tDO currentDIO = digitalSystem->readDO(status); |
| 566 | |
| 567 | if(port->port.pin < kNumHeaders) { |
| 568 | if(value == 0) { |
| 569 | currentDIO.Headers = currentDIO.Headers & ~(1 << port->port.pin); |
| 570 | } else if (value == 1) { |
| 571 | currentDIO.Headers = currentDIO.Headers | (1 << port->port.pin); |
| 572 | } |
| 573 | } else { |
| 574 | if(value == 0) { |
| 575 | currentDIO.MXP = currentDIO.MXP & ~(1 << remapMXPChannel(port->port.pin)); |
| 576 | } else if (value == 1) { |
| 577 | currentDIO.MXP = currentDIO.MXP | (1 << remapMXPChannel(port->port.pin)); |
| 578 | } |
| 579 | |
| 580 | uint32_t bitToSet = 1 << remapMXPChannel(port->port.pin); |
| 581 | short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status); |
| 582 | digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToSet, status); |
| 583 | } |
| 584 | digitalSystem->writeDO(currentDIO, status); |
| 585 | } |
| 586 | } |
| 587 | |
| 588 | /** |
| 589 | * Read a digital I/O bit from the FPGA. |
| 590 | * Get a single value from a digital I/O channel. |
| 591 | * |
| 592 | * @param channel The digital I/O channel |
| 593 | * @return The state of the specified channel |
| 594 | */ |
| 595 | bool getDIO(void* digital_port_pointer, int32_t *status) { |
| 596 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 597 | tDIO::tDI currentDIO = digitalSystem->readDI(status); |
| 598 | //Shift 00000001 over channel-1 places. |
| 599 | //AND it against the currentDIO |
| 600 | //if it == 0, then return false |
| 601 | //else return true |
| 602 | |
| 603 | if(port->port.pin < kNumHeaders) { |
| 604 | return ((currentDIO.Headers >> port->port.pin) & 1) != 0; |
| 605 | } else { |
| 606 | // Disable special functions |
| 607 | uint32_t bitToSet = 1 << remapMXPChannel(port->port.pin); |
| 608 | short specialFunctions = digitalSystem->readEnableMXPSpecialFunction(status); |
| 609 | digitalSystem->writeEnableMXPSpecialFunction(specialFunctions & ~bitToSet, status); |
| 610 | |
| 611 | return ((currentDIO.MXP >> remapMXPChannel(port->port.pin)) & 1) != 0; |
| 612 | } |
| 613 | } |
| 614 | |
| 615 | /** |
| 616 | * Read the direction of a the Digital I/O lines |
| 617 | * A 1 bit means output and a 0 bit means input. |
| 618 | * |
| 619 | * @param channel The digital I/O channel |
| 620 | * @return The direction of the specified channel |
| 621 | */ |
| 622 | bool getDIODirection(void* digital_port_pointer, int32_t *status) { |
| 623 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 624 | tDIO::tOutputEnable currentOutputEnable = digitalSystem->readOutputEnable(status); |
| 625 | //Shift 00000001 over port->port.pin-1 places. |
| 626 | //AND it against the currentOutputEnable |
| 627 | //if it == 0, then return false |
| 628 | //else return true |
| 629 | |
| 630 | if(port->port.pin < kNumHeaders) { |
| 631 | return ((currentOutputEnable.Headers >> port->port.pin) & 1) != 0; |
| 632 | } else { |
| 633 | return ((currentOutputEnable.MXP >> remapMXPChannel(port->port.pin)) & 1) != 0; |
| 634 | } |
| 635 | } |
| 636 | |
| 637 | /** |
| 638 | * Generate a single pulse. |
| 639 | * Write a pulse to the specified digital output channel. There can only be a single pulse going at any time. |
| 640 | * |
| 641 | * @param channel The Digital Output channel that the pulse should be output on |
| 642 | * @param pulseLength The active length of the pulse (in seconds) |
| 643 | */ |
| 644 | void pulse(void* digital_port_pointer, double pulseLength, int32_t *status) { |
| 645 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 646 | tDIO::tPulse pulse; |
| 647 | |
| 648 | if(port->port.pin < kNumHeaders) { |
| 649 | pulse.Headers = 1 << port->port.pin; |
| 650 | } else { |
| 651 | pulse.MXP = 1 << remapMXPChannel(port->port.pin); |
| 652 | } |
| 653 | |
| 654 | digitalSystem->writePulseLength((uint8_t)(1.0e9 * pulseLength / (pwmSystem->readLoopTiming(status) * 25)), status); |
| 655 | digitalSystem->writePulse(pulse, status); |
| 656 | } |
| 657 | |
| 658 | /** |
| 659 | * Check a DIO line to see if it is currently generating a pulse. |
| 660 | * |
| 661 | * @return A pulse is in progress |
| 662 | */ |
| 663 | bool isPulsing(void* digital_port_pointer, int32_t *status) { |
| 664 | DigitalPort* port = (DigitalPort*) digital_port_pointer; |
| 665 | tDIO::tPulse pulseRegister = digitalSystem->readPulse(status); |
| 666 | |
| 667 | if(port->port.pin < kNumHeaders) { |
| 668 | return (pulseRegister.Headers & (1 << port->port.pin)) != 0; |
| 669 | } else { |
| 670 | return (pulseRegister.MXP & (1 << remapMXPChannel(port->port.pin))) != 0; |
| 671 | } |
| 672 | } |
| 673 | |
| 674 | /** |
| 675 | * Check if any DIO line is currently generating a pulse. |
| 676 | * |
| 677 | * @return A pulse on some line is in progress |
| 678 | */ |
| 679 | bool isAnyPulsing(int32_t *status) { |
| 680 | tDIO::tPulse pulseRegister = digitalSystem->readPulse(status); |
| 681 | return pulseRegister.Headers != 0 && pulseRegister.MXP != 0; |
| 682 | } |
| 683 | |
| 684 | /** |
| 685 | * Write the filter index from the FPGA. |
| 686 | * Set the filter index used to filter out short pulses. |
| 687 | * |
| 688 | * @param digital_port_pointer The digital I/O channel |
| 689 | * @param filter_index The filter index. Must be in the range 0 - 3, |
| 690 | * where 0 means "none" and 1 - 3 means filter # filter_index - 1. |
| 691 | */ |
| 692 | void setFilterSelect(void* digital_port_pointer, int filter_index, |
| 693 | int32_t* status) { |
| 694 | DigitalPort* port = (DigitalPort*)digital_port_pointer; |
| 695 | |
| 696 | std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex); |
| 697 | if (port->port.pin < kNumHeaders) { |
| 698 | digitalSystem->writeFilterSelectHdr(port->port.pin, filter_index, status); |
| 699 | } |
| 700 | else { |
| 701 | digitalSystem->writeFilterSelectMXP(remapMXPChannel(port->port.pin), |
| 702 | filter_index, status); |
| 703 | } |
| 704 | } |
| 705 | |
| 706 | /** |
| 707 | * Read the filter index from the FPGA. |
| 708 | * Get the filter index used to filter out short pulses. |
| 709 | * |
| 710 | * @param digital_port_pointer The digital I/O channel |
| 711 | * @return filter_index The filter index. Must be in the range 0 - 3, |
| 712 | * where 0 means "none" and 1 - 3 means filter # filter_index - 1. |
| 713 | */ |
| 714 | int getFilterSelect(void* digital_port_pointer, int32_t* status) { |
| 715 | DigitalPort* port = (DigitalPort*)digital_port_pointer; |
| 716 | |
| 717 | std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex); |
| 718 | if (port->port.pin < kNumHeaders) { |
| 719 | return digitalSystem->readFilterSelectHdr(port->port.pin, status); |
| 720 | } |
| 721 | else { |
| 722 | return digitalSystem->readFilterSelectMXP(remapMXPChannel(port->port.pin), |
| 723 | status); |
| 724 | } |
| 725 | } |
| 726 | |
| 727 | /** |
| 728 | * Set the filter period for the specified filter index. |
| 729 | * |
| 730 | * Set the filter period in FPGA cycles. Even though there are 2 different |
| 731 | * filter index domains (MXP vs HDR), ignore that distinction for now since it |
| 732 | * compilicates the interface. That can be changed later. |
| 733 | * |
| 734 | * @param filter_index The filter index, 0 - 2. |
| 735 | * @param value The number of cycles that the signal must not transition to be |
| 736 | * counted as a transition. |
| 737 | */ |
| 738 | void setFilterPeriod(int filter_index, uint32_t value, int32_t* status) { |
| 739 | std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex); |
| 740 | digitalSystem->writeFilterPeriodHdr(filter_index, value, status); |
| 741 | if (*status == 0) { |
| 742 | digitalSystem->writeFilterPeriodMXP(filter_index, value, status); |
| 743 | } |
| 744 | } |
| 745 | |
| 746 | /** |
| 747 | * Get the filter period for the specified filter index. |
| 748 | * |
| 749 | * Get the filter period in FPGA cycles. Even though there are 2 different |
| 750 | * filter index domains (MXP vs HDR), ignore that distinction for now since it |
| 751 | * compilicates the interface. Set status to NiFpga_Status_SoftwareFault if the |
| 752 | * filter values miss-match. |
| 753 | * |
| 754 | * @param filter_index The filter index, 0 - 2. |
| 755 | * @param value The number of cycles that the signal must not transition to be |
| 756 | * counted as a transition. |
| 757 | */ |
| 758 | uint32_t getFilterPeriod(int filter_index, int32_t* status) { |
| 759 | uint32_t hdr_period = 0; |
| 760 | uint32_t mxp_period = 0; |
| 761 | { |
| 762 | std::lock_guard<priority_recursive_mutex> sync(digitalDIOMutex); |
| 763 | hdr_period = digitalSystem->readFilterPeriodHdr(filter_index, status); |
| 764 | if (*status == 0) { |
| 765 | mxp_period = digitalSystem->readFilterPeriodMXP(filter_index, status); |
| 766 | } |
| 767 | } |
| 768 | if (hdr_period != mxp_period) { |
| 769 | *status = NiFpga_Status_SoftwareFault; |
| 770 | return -1; |
| 771 | } |
| 772 | return hdr_period; |
| 773 | } |
| 774 | |
| 775 | struct counter_t { |
| 776 | tCounter* counter; |
| 777 | uint32_t index; |
| 778 | }; |
| 779 | typedef struct counter_t Counter; |
| 780 | |
| 781 | static hal::Resource *counters = NULL; |
| 782 | |
| 783 | void* initializeCounter(Mode mode, uint32_t *index, int32_t *status) { |
| 784 | hal::Resource::CreateResourceObject(&counters, tCounter::kNumSystems); |
| 785 | *index = counters->Allocate("Counter"); |
| 786 | if (*index == ~0ul) { |
| 787 | *status = NO_AVAILABLE_RESOURCES; |
| 788 | return NULL; |
| 789 | } |
| 790 | Counter* counter = new Counter(); |
| 791 | counter->counter = tCounter::create(*index, status); |
| 792 | counter->counter->writeConfig_Mode(mode, status); |
| 793 | counter->counter->writeTimerConfig_AverageSize(1, status); |
| 794 | counter->index = *index; |
| 795 | return counter; |
| 796 | } |
| 797 | |
| 798 | void freeCounter(void* counter_pointer, int32_t *status) { |
| 799 | Counter* counter = (Counter*) counter_pointer; |
| 800 | if (!counter) return; |
| 801 | delete counter->counter; |
| 802 | counters->Free(counter->index); |
| 803 | } |
| 804 | |
| 805 | void setCounterAverageSize(void* counter_pointer, int32_t size, int32_t *status) { |
| 806 | Counter* counter = (Counter*) counter_pointer; |
| 807 | counter->counter->writeTimerConfig_AverageSize(size, status); |
| 808 | } |
| 809 | |
| 810 | /** |
| 811 | * remap the digital source pin and set the module. |
| 812 | * If it's an analog trigger, determine the module from the high order routing channel |
| 813 | * else do normal digital input remapping based on pin number (MXP) |
| 814 | */ |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 815 | extern "C++" void remapDigitalSource(bool analogTrigger, uint32_t &pin, uint8_t &module) { |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 816 | if (analogTrigger) { |
| 817 | module = pin >> 4; |
| 818 | } else { |
| 819 | if(pin >= kNumHeaders) { |
| 820 | pin = remapMXPChannel(pin); |
| 821 | module = 1; |
| 822 | } else { |
| 823 | module = 0; |
| 824 | } |
| 825 | } |
| 826 | } |
| 827 | |
| 828 | /** |
| 829 | * Set the source object that causes the counter to count up. |
| 830 | * Set the up counting DigitalSource. |
| 831 | */ |
| 832 | void setCounterUpSource(void* counter_pointer, uint32_t pin, bool analogTrigger, int32_t *status) { |
| 833 | Counter* counter = (Counter*) counter_pointer; |
| 834 | |
| 835 | uint8_t module; |
| 836 | |
| 837 | remapDigitalSource(analogTrigger, pin, module); |
| 838 | |
| 839 | counter->counter->writeConfig_UpSource_Module(module, status); |
| 840 | counter->counter->writeConfig_UpSource_Channel(pin, status); |
| 841 | counter->counter->writeConfig_UpSource_AnalogTrigger(analogTrigger, status); |
| 842 | |
| 843 | if(counter->counter->readConfig_Mode(status) == kTwoPulse || |
| 844 | counter->counter->readConfig_Mode(status) == kExternalDirection) { |
| 845 | setCounterUpSourceEdge(counter_pointer, true, false, status); |
| 846 | } |
| 847 | counter->counter->strobeReset(status); |
| 848 | } |
| 849 | |
| 850 | /** |
| 851 | * Set the edge sensitivity on an up counting source. |
| 852 | * Set the up source to either detect rising edges or falling edges. |
| 853 | */ |
| 854 | void setCounterUpSourceEdge(void* counter_pointer, bool risingEdge, bool fallingEdge, int32_t *status) { |
| 855 | Counter* counter = (Counter*) counter_pointer; |
| 856 | counter->counter->writeConfig_UpRisingEdge(risingEdge, status); |
| 857 | counter->counter->writeConfig_UpFallingEdge(fallingEdge, status); |
| 858 | } |
| 859 | |
| 860 | /** |
| 861 | * Disable the up counting source to the counter. |
| 862 | */ |
| 863 | void clearCounterUpSource(void* counter_pointer, int32_t *status) { |
| 864 | Counter* counter = (Counter*) counter_pointer; |
| 865 | counter->counter->writeConfig_UpFallingEdge(false, status); |
| 866 | counter->counter->writeConfig_UpRisingEdge(false, status); |
| 867 | // Index 0 of digital is always 0. |
| 868 | counter->counter->writeConfig_UpSource_Channel(0, status); |
| 869 | counter->counter->writeConfig_UpSource_AnalogTrigger(false, status); |
| 870 | } |
| 871 | |
| 872 | /** |
| 873 | * Set the source object that causes the counter to count down. |
| 874 | * Set the down counting DigitalSource. |
| 875 | */ |
| 876 | void setCounterDownSource(void* counter_pointer, uint32_t pin, bool analogTrigger, int32_t *status) { |
| 877 | Counter* counter = (Counter*) counter_pointer; |
| 878 | unsigned char mode = counter->counter->readConfig_Mode(status); |
| 879 | if (mode != kTwoPulse && mode != kExternalDirection) { |
| 880 | // TODO: wpi_setWPIErrorWithContext(ParameterOutOfRange, "Counter only supports DownSource in TwoPulse and ExternalDirection modes."); |
| 881 | *status = PARAMETER_OUT_OF_RANGE; |
| 882 | return; |
| 883 | } |
| 884 | |
| 885 | uint8_t module; |
| 886 | |
| 887 | remapDigitalSource(analogTrigger, pin, module); |
| 888 | |
| 889 | counter->counter->writeConfig_DownSource_Module(module, status); |
| 890 | counter->counter->writeConfig_DownSource_Channel(pin, status); |
| 891 | counter->counter->writeConfig_DownSource_AnalogTrigger(analogTrigger, status); |
| 892 | |
| 893 | setCounterDownSourceEdge(counter_pointer, true, false, status); |
| 894 | counter->counter->strobeReset(status); |
| 895 | } |
| 896 | |
| 897 | /** |
| 898 | * Set the edge sensitivity on a down counting source. |
| 899 | * Set the down source to either detect rising edges or falling edges. |
| 900 | */ |
| 901 | void setCounterDownSourceEdge(void* counter_pointer, bool risingEdge, bool fallingEdge, int32_t *status) { |
| 902 | Counter* counter = (Counter*) counter_pointer; |
| 903 | counter->counter->writeConfig_DownRisingEdge(risingEdge, status); |
| 904 | counter->counter->writeConfig_DownFallingEdge(fallingEdge, status); |
| 905 | } |
| 906 | |
| 907 | /** |
| 908 | * Disable the down counting source to the counter. |
| 909 | */ |
| 910 | void clearCounterDownSource(void* counter_pointer, int32_t *status) { |
| 911 | Counter* counter = (Counter*) counter_pointer; |
| 912 | counter->counter->writeConfig_DownFallingEdge(false, status); |
| 913 | counter->counter->writeConfig_DownRisingEdge(false, status); |
| 914 | // Index 0 of digital is always 0. |
| 915 | counter->counter->writeConfig_DownSource_Channel(0, status); |
| 916 | counter->counter->writeConfig_DownSource_AnalogTrigger(false, status); |
| 917 | } |
| 918 | |
| 919 | /** |
| 920 | * Set standard up / down counting mode on this counter. |
| 921 | * Up and down counts are sourced independently from two inputs. |
| 922 | */ |
| 923 | void setCounterUpDownMode(void* counter_pointer, int32_t *status) { |
| 924 | Counter* counter = (Counter*) counter_pointer; |
| 925 | counter->counter->writeConfig_Mode(kTwoPulse, status); |
| 926 | } |
| 927 | |
| 928 | /** |
| 929 | * Set external direction mode on this counter. |
| 930 | * Counts are sourced on the Up counter input. |
| 931 | * The Down counter input represents the direction to count. |
| 932 | */ |
| 933 | void setCounterExternalDirectionMode(void* counter_pointer, int32_t *status) { |
| 934 | Counter* counter = (Counter*) counter_pointer; |
| 935 | counter->counter->writeConfig_Mode(kExternalDirection, status); |
| 936 | } |
| 937 | |
| 938 | /** |
| 939 | * Set Semi-period mode on this counter. |
| 940 | * Counts up on both rising and falling edges. |
| 941 | */ |
| 942 | void setCounterSemiPeriodMode(void* counter_pointer, bool highSemiPeriod, int32_t *status) { |
| 943 | Counter* counter = (Counter*) counter_pointer; |
| 944 | counter->counter->writeConfig_Mode(kSemiperiod, status); |
| 945 | counter->counter->writeConfig_UpRisingEdge(highSemiPeriod, status); |
| 946 | setCounterUpdateWhenEmpty(counter_pointer, false, status); |
| 947 | } |
| 948 | |
| 949 | /** |
| 950 | * Configure the counter to count in up or down based on the length of the input pulse. |
| 951 | * This mode is most useful for direction sensitive gear tooth sensors. |
| 952 | * @param threshold The pulse length beyond which the counter counts the opposite direction. Units are seconds. |
| 953 | */ |
| 954 | void setCounterPulseLengthMode(void* counter_pointer, double threshold, int32_t *status) { |
| 955 | Counter* counter = (Counter*) counter_pointer; |
| 956 | counter->counter->writeConfig_Mode(kPulseLength, status); |
| 957 | counter->counter->writeConfig_PulseLengthThreshold((uint32_t)(threshold * 1.0e6) * kSystemClockTicksPerMicrosecond, status); |
| 958 | } |
| 959 | |
| 960 | /** |
| 961 | * Get the Samples to Average which specifies the number of samples of the timer to |
| 962 | * average when calculating the period. Perform averaging to account for |
| 963 | * mechanical imperfections or as oversampling to increase resolution. |
| 964 | * @return SamplesToAverage The number of samples being averaged (from 1 to 127) |
| 965 | */ |
| 966 | int32_t getCounterSamplesToAverage(void* counter_pointer, int32_t *status) { |
| 967 | Counter* counter = (Counter*) counter_pointer; |
| 968 | return counter->counter->readTimerConfig_AverageSize(status); |
| 969 | } |
| 970 | |
| 971 | /** |
| 972 | * Set the Samples to Average which specifies the number of samples of the timer to |
| 973 | * average when calculating the period. Perform averaging to account for |
| 974 | * mechanical imperfections or as oversampling to increase resolution. |
| 975 | * @param samplesToAverage The number of samples to average from 1 to 127. |
| 976 | */ |
| 977 | void setCounterSamplesToAverage(void* counter_pointer, int samplesToAverage, int32_t *status) { |
| 978 | Counter* counter = (Counter*) counter_pointer; |
| 979 | if (samplesToAverage < 1 || samplesToAverage > 127) { |
| 980 | *status = PARAMETER_OUT_OF_RANGE; |
| 981 | } |
| 982 | counter->counter->writeTimerConfig_AverageSize(samplesToAverage, status); |
| 983 | } |
| 984 | |
| 985 | /** |
| 986 | * Reset the Counter to zero. |
| 987 | * Set the counter value to zero. This doesn't effect the running state of the counter, just sets |
| 988 | * the current value to zero. |
| 989 | */ |
| 990 | void resetCounter(void* counter_pointer, int32_t *status) { |
| 991 | Counter* counter = (Counter*) counter_pointer; |
| 992 | counter->counter->strobeReset(status); |
| 993 | } |
| 994 | |
| 995 | /** |
| 996 | * Read the current counter value. |
| 997 | * Read the value at this instant. It may still be running, so it reflects the current value. Next |
| 998 | * time it is read, it might have a different value. |
| 999 | */ |
| 1000 | int32_t getCounter(void* counter_pointer, int32_t *status) { |
| 1001 | Counter* counter = (Counter*) counter_pointer; |
| 1002 | int32_t value = counter->counter->readOutput_Value(status); |
| 1003 | return value; |
| 1004 | } |
| 1005 | |
| 1006 | /* |
| 1007 | * Get the Period of the most recent count. |
| 1008 | * Returns the time interval of the most recent count. This can be used for velocity calculations |
| 1009 | * to determine shaft speed. |
| 1010 | * @returns The period of the last two pulses in units of seconds. |
| 1011 | */ |
| 1012 | double getCounterPeriod(void* counter_pointer, int32_t *status) { |
| 1013 | Counter* counter = (Counter*) counter_pointer; |
| 1014 | tCounter::tTimerOutput output = counter->counter->readTimerOutput(status); |
| 1015 | double period; |
| 1016 | if (output.Stalled) { |
| 1017 | // Return infinity |
| 1018 | double zero = 0.0; |
| 1019 | period = 1.0 / zero; |
| 1020 | } else { |
| 1021 | // output.Period is a fixed point number that counts by 2 (24 bits, 25 integer bits) |
| 1022 | period = (double)(output.Period << 1) / (double)output.Count; |
| 1023 | } |
| 1024 | return period * 2.5e-8; // result * timebase (currently 40ns) |
| 1025 | } |
| 1026 | |
| 1027 | /** |
| 1028 | * Set the maximum period where the device is still considered "moving". |
| 1029 | * Sets the maximum period where the device is considered moving. This value is used to determine |
| 1030 | * the "stopped" state of the counter using the GetStopped method. |
| 1031 | * @param maxPeriod The maximum period where the counted device is considered moving in |
| 1032 | * seconds. |
| 1033 | */ |
| 1034 | void setCounterMaxPeriod(void* counter_pointer, double maxPeriod, int32_t *status) { |
| 1035 | Counter* counter = (Counter*) counter_pointer; |
| 1036 | counter->counter->writeTimerConfig_StallPeriod((uint32_t)(maxPeriod * 4.0e8), status); |
| 1037 | } |
| 1038 | |
| 1039 | /** |
| 1040 | * Select whether you want to continue updating the event timer output when there are no samples captured. |
| 1041 | * The output of the event timer has a buffer of periods that are averaged and posted to |
| 1042 | * a register on the FPGA. When the timer detects that the event source has stopped |
| 1043 | * (based on the MaxPeriod) the buffer of samples to be averaged is emptied. If you |
| 1044 | * enable the update when empty, you will be notified of the stopped source and the event |
| 1045 | * time will report 0 samples. If you disable update when empty, the most recent average |
| 1046 | * will remain on the output until a new sample is acquired. You will never see 0 samples |
| 1047 | * output (except when there have been no events since an FPGA reset) and you will likely not |
| 1048 | * see the stopped bit become true (since it is updated at the end of an average and there are |
| 1049 | * no samples to average). |
| 1050 | */ |
| 1051 | void setCounterUpdateWhenEmpty(void* counter_pointer, bool enabled, int32_t *status) { |
| 1052 | Counter* counter = (Counter*) counter_pointer; |
| 1053 | counter->counter->writeTimerConfig_UpdateWhenEmpty(enabled, status); |
| 1054 | } |
| 1055 | |
| 1056 | /** |
| 1057 | * Determine if the clock is stopped. |
| 1058 | * Determine if the clocked input is stopped based on the MaxPeriod value set using the |
| 1059 | * SetMaxPeriod method. If the clock exceeds the MaxPeriod, then the device (and counter) are |
| 1060 | * assumed to be stopped and it returns true. |
| 1061 | * @return Returns true if the most recent counter period exceeds the MaxPeriod value set by |
| 1062 | * SetMaxPeriod. |
| 1063 | */ |
| 1064 | bool getCounterStopped(void* counter_pointer, int32_t *status) { |
| 1065 | Counter* counter = (Counter*) counter_pointer; |
| 1066 | return counter->counter->readTimerOutput_Stalled(status); |
| 1067 | } |
| 1068 | |
| 1069 | /** |
| 1070 | * The last direction the counter value changed. |
| 1071 | * @return The last direction the counter value changed. |
| 1072 | */ |
| 1073 | bool getCounterDirection(void* counter_pointer, int32_t *status) { |
| 1074 | Counter* counter = (Counter*) counter_pointer; |
| 1075 | bool value = counter->counter->readOutput_Direction(status); |
| 1076 | return value; |
| 1077 | } |
| 1078 | |
| 1079 | /** |
| 1080 | * Set the Counter to return reversed sensing on the direction. |
| 1081 | * This allows counters to change the direction they are counting in the case of 1X and 2X |
| 1082 | * quadrature encoding only. Any other counter mode isn't supported. |
| 1083 | * @param reverseDirection true if the value counted should be negated. |
| 1084 | */ |
| 1085 | void setCounterReverseDirection(void* counter_pointer, bool reverseDirection, int32_t *status) { |
| 1086 | Counter* counter = (Counter*) counter_pointer; |
| 1087 | if (counter->counter->readConfig_Mode(status) == kExternalDirection) { |
| 1088 | if (reverseDirection) |
| 1089 | setCounterDownSourceEdge(counter_pointer, true, true, status); |
| 1090 | else |
| 1091 | setCounterDownSourceEdge(counter_pointer, false, true, status); |
| 1092 | } |
| 1093 | } |
| 1094 | |
| 1095 | struct encoder_t { |
| 1096 | tEncoder* encoder; |
| 1097 | uint32_t index; |
| 1098 | }; |
| 1099 | typedef struct encoder_t Encoder; |
| 1100 | |
| 1101 | static const double DECODING_SCALING_FACTOR = 0.25; |
| 1102 | static hal::Resource *quadEncoders = NULL; |
| 1103 | |
| 1104 | void* initializeEncoder(uint8_t port_a_module, uint32_t port_a_pin, bool port_a_analog_trigger, |
| 1105 | uint8_t port_b_module, uint32_t port_b_pin, bool port_b_analog_trigger, |
| 1106 | bool reverseDirection, int32_t *index, int32_t *status) { |
| 1107 | |
| 1108 | // Initialize encoder structure |
| 1109 | Encoder* encoder = new Encoder(); |
| 1110 | |
| 1111 | remapDigitalSource(port_a_analog_trigger, port_a_pin, port_a_module); |
| 1112 | remapDigitalSource(port_b_analog_trigger, port_b_pin, port_b_module); |
| 1113 | |
| 1114 | hal::Resource::CreateResourceObject(&quadEncoders, tEncoder::kNumSystems); |
| 1115 | encoder->index = quadEncoders->Allocate("4X Encoder"); |
| 1116 | *index = encoder->index; |
| 1117 | // TODO: if (index == ~0ul) { CloneError(quadEncoders); return; } |
| 1118 | encoder->encoder = tEncoder::create(encoder->index, status); |
| 1119 | encoder->encoder->writeConfig_ASource_Module(port_a_module, status); |
| 1120 | encoder->encoder->writeConfig_ASource_Channel(port_a_pin, status); |
| 1121 | encoder->encoder->writeConfig_ASource_AnalogTrigger(port_a_analog_trigger, status); |
| 1122 | encoder->encoder->writeConfig_BSource_Module(port_b_module, status); |
| 1123 | encoder->encoder->writeConfig_BSource_Channel(port_b_pin, status); |
| 1124 | encoder->encoder->writeConfig_BSource_AnalogTrigger(port_b_analog_trigger, status); |
| 1125 | encoder->encoder->strobeReset(status); |
| 1126 | encoder->encoder->writeConfig_Reverse(reverseDirection, status); |
| 1127 | encoder->encoder->writeTimerConfig_AverageSize(4, status); |
| 1128 | |
| 1129 | return encoder; |
| 1130 | } |
| 1131 | |
| 1132 | void freeEncoder(void* encoder_pointer, int32_t *status) { |
| 1133 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1134 | if (!encoder) return; |
| 1135 | quadEncoders->Free(encoder->index); |
| 1136 | delete encoder->encoder; |
| 1137 | } |
| 1138 | |
| 1139 | /** |
| 1140 | * Reset the Encoder distance to zero. |
| 1141 | * Resets the current count to zero on the encoder. |
| 1142 | */ |
| 1143 | void resetEncoder(void* encoder_pointer, int32_t *status) { |
| 1144 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1145 | encoder->encoder->strobeReset(status); |
| 1146 | } |
| 1147 | |
| 1148 | /** |
| 1149 | * Gets the raw value from the encoder. |
| 1150 | * The raw value is the actual count unscaled by the 1x, 2x, or 4x scale |
| 1151 | * factor. |
| 1152 | * @return Current raw count from the encoder |
| 1153 | */ |
| 1154 | int32_t getEncoder(void* encoder_pointer, int32_t *status) { |
| 1155 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1156 | return encoder->encoder->readOutput_Value(status); |
| 1157 | } |
| 1158 | |
| 1159 | /** |
| 1160 | * Returns the period of the most recent pulse. |
| 1161 | * Returns the period of the most recent Encoder pulse in seconds. |
| 1162 | * This method compenstates for the decoding type. |
| 1163 | * |
| 1164 | * @deprecated Use GetRate() in favor of this method. This returns unscaled periods and GetRate() scales using value from SetDistancePerPulse(). |
| 1165 | * |
| 1166 | * @return Period in seconds of the most recent pulse. |
| 1167 | */ |
| 1168 | double getEncoderPeriod(void* encoder_pointer, int32_t *status) { |
| 1169 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1170 | tEncoder::tTimerOutput output = encoder->encoder->readTimerOutput(status); |
| 1171 | double value; |
| 1172 | if (output.Stalled) { |
| 1173 | // Return infinity |
| 1174 | double zero = 0.0; |
| 1175 | value = 1.0 / zero; |
| 1176 | } else { |
| 1177 | // output.Period is a fixed point number that counts by 2 (24 bits, 25 integer bits) |
| 1178 | value = (double)(output.Period << 1) / (double)output.Count; |
| 1179 | } |
| 1180 | double measuredPeriod = value * 2.5e-8; |
| 1181 | return measuredPeriod / DECODING_SCALING_FACTOR; |
| 1182 | } |
| 1183 | |
| 1184 | /** |
| 1185 | * Sets the maximum period for stopped detection. |
| 1186 | * Sets the value that represents the maximum period of the Encoder before it will assume |
| 1187 | * that the attached device is stopped. This timeout allows users to determine if the wheels or |
| 1188 | * other shaft has stopped rotating. |
| 1189 | * This method compensates for the decoding type. |
| 1190 | * |
| 1191 | * @deprecated Use SetMinRate() in favor of this method. This takes unscaled periods and SetMinRate() scales using value from SetDistancePerPulse(). |
| 1192 | * |
| 1193 | * @param maxPeriod The maximum time between rising and falling edges before the FPGA will |
| 1194 | * report the device stopped. This is expressed in seconds. |
| 1195 | */ |
| 1196 | void setEncoderMaxPeriod(void* encoder_pointer, double maxPeriod, int32_t *status) { |
| 1197 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1198 | encoder->encoder->writeTimerConfig_StallPeriod((uint32_t)(maxPeriod * 4.0e8 * DECODING_SCALING_FACTOR), status); |
| 1199 | } |
| 1200 | |
| 1201 | /** |
| 1202 | * Determine if the encoder is stopped. |
| 1203 | * Using the MaxPeriod value, a boolean is returned that is true if the encoder is considered |
| 1204 | * stopped and false if it is still moving. A stopped encoder is one where the most recent pulse |
| 1205 | * width exceeds the MaxPeriod. |
| 1206 | * @return True if the encoder is considered stopped. |
| 1207 | */ |
| 1208 | bool getEncoderStopped(void* encoder_pointer, int32_t *status) { |
| 1209 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1210 | return encoder->encoder->readTimerOutput_Stalled(status) != 0; |
| 1211 | } |
| 1212 | |
| 1213 | /** |
| 1214 | * The last direction the encoder value changed. |
| 1215 | * @return The last direction the encoder value changed. |
| 1216 | */ |
| 1217 | bool getEncoderDirection(void* encoder_pointer, int32_t *status) { |
| 1218 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1219 | return encoder->encoder->readOutput_Direction(status); |
| 1220 | } |
| 1221 | |
| 1222 | /** |
| 1223 | * Set the direction sensing for this encoder. |
| 1224 | * This sets the direction sensing on the encoder so that it could count in the correct |
| 1225 | * software direction regardless of the mounting. |
| 1226 | * @param reverseDirection true if the encoder direction should be reversed |
| 1227 | */ |
| 1228 | void setEncoderReverseDirection(void* encoder_pointer, bool reverseDirection, int32_t *status) { |
| 1229 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1230 | encoder->encoder->writeConfig_Reverse(reverseDirection, status); |
| 1231 | } |
| 1232 | |
| 1233 | /** |
| 1234 | * Set the Samples to Average which specifies the number of samples of the timer to |
| 1235 | * average when calculating the period. Perform averaging to account for |
| 1236 | * mechanical imperfections or as oversampling to increase resolution. |
| 1237 | * @param samplesToAverage The number of samples to average from 1 to 127. |
| 1238 | */ |
| 1239 | void setEncoderSamplesToAverage(void* encoder_pointer, uint32_t samplesToAverage, int32_t *status) { |
| 1240 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1241 | if (samplesToAverage < 1 || samplesToAverage > 127) { |
| 1242 | *status = PARAMETER_OUT_OF_RANGE; |
| 1243 | } |
| 1244 | encoder->encoder->writeTimerConfig_AverageSize(samplesToAverage, status); |
| 1245 | } |
| 1246 | |
| 1247 | /** |
| 1248 | * Get the Samples to Average which specifies the number of samples of the timer to |
| 1249 | * average when calculating the period. Perform averaging to account for |
| 1250 | * mechanical imperfections or as oversampling to increase resolution. |
| 1251 | * @return SamplesToAverage The number of samples being averaged (from 1 to 127) |
| 1252 | */ |
| 1253 | uint32_t getEncoderSamplesToAverage(void* encoder_pointer, int32_t *status) { |
| 1254 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1255 | return encoder->encoder->readTimerConfig_AverageSize(status); |
| 1256 | } |
| 1257 | |
| 1258 | /** |
| 1259 | * Set an index source for an encoder, which is an input that resets the |
| 1260 | * encoder's count. |
| 1261 | */ |
| 1262 | void setEncoderIndexSource(void *encoder_pointer, uint32_t pin, bool analogTrigger, bool activeHigh, |
| 1263 | bool edgeSensitive, int32_t *status) { |
| 1264 | Encoder* encoder = (Encoder*) encoder_pointer; |
| 1265 | encoder->encoder->writeConfig_IndexSource_Channel((unsigned char)pin, status); |
| 1266 | encoder->encoder->writeConfig_IndexSource_Module((unsigned char)0, status); |
| 1267 | encoder->encoder->writeConfig_IndexSource_AnalogTrigger(analogTrigger, status); |
| 1268 | encoder->encoder->writeConfig_IndexActiveHigh(activeHigh, status); |
| 1269 | encoder->encoder->writeConfig_IndexEdgeSensitive(edgeSensitive, status); |
| 1270 | } |
| 1271 | |
| 1272 | /** |
| 1273 | * Get the loop timing of the PWM system |
| 1274 | * |
| 1275 | * @return The loop time |
| 1276 | */ |
| 1277 | uint16_t getLoopTiming(int32_t *status) { |
| 1278 | return pwmSystem->readLoopTiming(status); |
| 1279 | } |
| 1280 | |
| 1281 | /* |
| 1282 | * Initialize the spi port. Opens the port if necessary and saves the handle. |
| 1283 | * If opening the MXP port, also sets up the pin functions appropriately |
| 1284 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1285 | */ |
| 1286 | void spiInitialize(uint8_t port, int32_t *status) { |
| 1287 | if(spiSystem == NULL) |
| 1288 | spiSystem = tSPI::create(status); |
| 1289 | if(spiGetHandle(port) !=0 ) return; |
| 1290 | switch(port){ |
| 1291 | case 0: |
| 1292 | spiSetHandle(0, spilib_open("/dev/spidev0.0")); |
| 1293 | break; |
| 1294 | case 1: |
| 1295 | spiSetHandle(1, spilib_open("/dev/spidev0.1")); |
| 1296 | break; |
| 1297 | case 2: |
| 1298 | spiSetHandle(2, spilib_open("/dev/spidev0.2")); |
| 1299 | break; |
| 1300 | case 3: |
| 1301 | spiSetHandle(3, spilib_open("/dev/spidev0.3")); |
| 1302 | break; |
| 1303 | case 4: |
| 1304 | initializeDigital(status); |
| 1305 | if(!allocateDIO(getPort(14), false, status)){printf("Failed to allocate DIO 14\n"); return;} |
| 1306 | if(!allocateDIO(getPort(15), false, status)) {printf("Failed to allocate DIO 15\n"); return;} |
| 1307 | if(!allocateDIO(getPort(16), true, status)) {printf("Failed to allocate DIO 16\n"); return;} |
| 1308 | if(!allocateDIO(getPort(17), false, status)) {printf("Failed to allocate DIO 17\n"); return;} |
| 1309 | digitalSystem->writeEnableMXPSpecialFunction(digitalSystem->readEnableMXPSpecialFunction(status)|0x00F0, status); |
| 1310 | spiSetHandle(4, spilib_open("/dev/spidev1.0")); |
| 1311 | break; |
| 1312 | default: |
| 1313 | break; |
| 1314 | } |
| 1315 | return; |
| 1316 | } |
| 1317 | |
| 1318 | /** |
| 1319 | * Generic transaction. |
| 1320 | * |
| 1321 | * This is a lower-level interface to the spi hardware giving you more control over each transaction. |
| 1322 | * |
| 1323 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1324 | * @param dataToSend Buffer of data to send as part of the transaction. |
| 1325 | * @param dataReceived Buffer to read data into. |
| 1326 | * @param size Number of bytes to transfer. [0..7] |
| 1327 | * @return Number of bytes transferred, -1 for error |
| 1328 | */ |
| 1329 | int32_t spiTransaction(uint8_t port, uint8_t *dataToSend, uint8_t *dataReceived, uint8_t size) |
| 1330 | { |
| 1331 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1332 | return spilib_writeread(spiGetHandle(port), (const char*) dataToSend, (char*) dataReceived, (int32_t) size); |
| 1333 | } |
| 1334 | |
| 1335 | /** |
| 1336 | * Execute a write transaction with the device. |
| 1337 | * |
| 1338 | * Write to a device and wait until the transaction is complete. |
| 1339 | * |
| 1340 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1341 | * @param datToSend The data to write to the register on the device. |
| 1342 | * @param sendSize The number of bytes to be written |
| 1343 | * @return The number of bytes written. -1 for an error |
| 1344 | */ |
| 1345 | int32_t spiWrite(uint8_t port, uint8_t* dataToSend, uint8_t sendSize) |
| 1346 | { |
| 1347 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1348 | return spilib_write(spiGetHandle(port), (const char*) dataToSend, (int32_t) sendSize); |
| 1349 | } |
| 1350 | |
| 1351 | /** |
| 1352 | * Execute a read from the device. |
| 1353 | * |
| 1354 | * This methdod does not write any data out to the device |
| 1355 | * Most spi devices will require a register address to be written before |
| 1356 | * they begin returning data |
| 1357 | * |
| 1358 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1359 | * @param buffer A pointer to the array of bytes to store the data read from the device. |
| 1360 | * @param count The number of bytes to read in the transaction. [1..7] |
| 1361 | * @return Number of bytes read. -1 for error. |
| 1362 | */ |
| 1363 | int32_t spiRead(uint8_t port, uint8_t *buffer, uint8_t count) |
| 1364 | { |
| 1365 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1366 | return spilib_read(spiGetHandle(port), (char*) buffer, (int32_t) count); |
| 1367 | } |
| 1368 | |
| 1369 | /** |
| 1370 | * Close the SPI port |
| 1371 | * |
| 1372 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1373 | */ |
| 1374 | void spiClose(uint8_t port) { |
| 1375 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1376 | if (spiAccumulators[port]) { |
| 1377 | int32_t status = 0; |
| 1378 | spiFreeAccumulator(port, &status); |
| 1379 | } |
| 1380 | spilib_close(spiGetHandle(port)); |
| 1381 | spiSetHandle(port, 0); |
| 1382 | return; |
| 1383 | } |
| 1384 | |
| 1385 | /** |
| 1386 | * Set the clock speed for the SPI bus. |
| 1387 | * |
| 1388 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1389 | * @param speed The speed in Hz (0-1MHz) |
| 1390 | */ |
| 1391 | void spiSetSpeed(uint8_t port, uint32_t speed) { |
| 1392 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1393 | spilib_setspeed(spiGetHandle(port), speed); |
| 1394 | } |
| 1395 | |
| 1396 | /** |
| 1397 | * Set the SPI options |
| 1398 | * |
| 1399 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1400 | * @param msb_first True to write the MSB first, False for LSB first |
| 1401 | * @param sample_on_trailing True to sample on the trailing edge, False to sample on the leading edge |
| 1402 | * @param clk_idle_high True to set the clock to active low, False to set the clock active high |
| 1403 | */ |
| 1404 | void spiSetOpts(uint8_t port, int msb_first, int sample_on_trailing, int clk_idle_high) { |
| 1405 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1406 | spilib_setopts(spiGetHandle(port), msb_first, sample_on_trailing, clk_idle_high); |
| 1407 | } |
| 1408 | |
| 1409 | /** |
| 1410 | * Set the CS Active high for a SPI port |
| 1411 | * |
| 1412 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1413 | */ |
| 1414 | void spiSetChipSelectActiveHigh(uint8_t port, int32_t *status){ |
| 1415 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1416 | if(port < 4) |
| 1417 | { |
| 1418 | spiSystem->writeChipSelectActiveHigh_Hdr(spiSystem->readChipSelectActiveHigh_Hdr(status) | (1<<port), status); |
| 1419 | } |
| 1420 | else |
| 1421 | { |
| 1422 | spiSystem->writeChipSelectActiveHigh_MXP(1, status); |
| 1423 | } |
| 1424 | } |
| 1425 | |
| 1426 | /** |
| 1427 | * Set the CS Active low for a SPI port |
| 1428 | * |
| 1429 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1430 | */ |
| 1431 | void spiSetChipSelectActiveLow(uint8_t port, int32_t *status){ |
| 1432 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1433 | if(port < 4) |
| 1434 | { |
| 1435 | spiSystem->writeChipSelectActiveHigh_Hdr(spiSystem->readChipSelectActiveHigh_Hdr(status) & ~(1<<port), status); |
| 1436 | } |
| 1437 | else |
| 1438 | { |
| 1439 | spiSystem->writeChipSelectActiveHigh_MXP(0, status); |
| 1440 | } |
| 1441 | } |
| 1442 | |
| 1443 | /** |
| 1444 | * Get the stored handle for a SPI port |
| 1445 | * |
| 1446 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1447 | * @return The stored handle for the SPI port. 0 represents no stored handle. |
| 1448 | */ |
| 1449 | int32_t spiGetHandle(uint8_t port){ |
| 1450 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1451 | switch(port){ |
| 1452 | case 0: |
| 1453 | return m_spiCS0Handle; |
| 1454 | case 1: |
| 1455 | return m_spiCS1Handle; |
| 1456 | case 2: |
| 1457 | return m_spiCS2Handle; |
| 1458 | case 3: |
| 1459 | return m_spiCS3Handle; |
| 1460 | case 4: |
| 1461 | return m_spiMXPHandle; |
| 1462 | default: |
| 1463 | return 0; |
| 1464 | } |
| 1465 | } |
| 1466 | |
| 1467 | /** |
| 1468 | * Set the stored handle for a SPI port |
| 1469 | * |
| 1470 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP. |
| 1471 | * @param handle The value of the handle for the port. |
| 1472 | */ |
| 1473 | void spiSetHandle(uint8_t port, int32_t handle){ |
| 1474 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1475 | switch(port){ |
| 1476 | case 0: |
| 1477 | m_spiCS0Handle = handle; |
| 1478 | break; |
| 1479 | case 1: |
| 1480 | m_spiCS1Handle = handle; |
| 1481 | break; |
| 1482 | case 2: |
| 1483 | m_spiCS2Handle = handle; |
| 1484 | break; |
| 1485 | case 3: |
| 1486 | m_spiCS3Handle = handle; |
| 1487 | break; |
| 1488 | case 4: |
| 1489 | m_spiMXPHandle = handle; |
| 1490 | break; |
| 1491 | default: |
| 1492 | break; |
| 1493 | } |
| 1494 | } |
| 1495 | |
| 1496 | /** |
| 1497 | * Get the semaphore for a SPI port |
| 1498 | * |
| 1499 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 1500 | * @return The semaphore for the SPI port. |
| 1501 | */ |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 1502 | extern "C++" priority_recursive_mutex& spiGetSemaphore(uint8_t port) { |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 1503 | if(port < 4) |
| 1504 | return spiOnboardSemaphore; |
| 1505 | else |
| 1506 | return spiMXPSemaphore; |
| 1507 | } |
| 1508 | |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 1509 | static void spiAccumulatorProcess(uint64_t currentTime, void *param) { |
Brian Silverman | 26e4e52 | 2015-12-17 01:56:40 -0500 | [diff] [blame] | 1510 | SPIAccumulator* accum = (SPIAccumulator*)param; |
| 1511 | |
| 1512 | // perform SPI transaction |
| 1513 | uint8_t resp_b[4]; |
| 1514 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(accum->port)); |
| 1515 | spilib_writeread(spiGetHandle(accum->port), (const char*) accum->cmd, (char*) resp_b, (int32_t) accum->xfer_size); |
| 1516 | |
| 1517 | // convert from bytes |
| 1518 | uint32_t resp = 0; |
| 1519 | if (accum->big_endian) { |
| 1520 | for (int i=0; i < accum->xfer_size; ++i) { |
| 1521 | resp <<= 8; |
| 1522 | resp |= resp_b[i] & 0xff; |
| 1523 | } |
| 1524 | } else { |
| 1525 | for (int i = accum->xfer_size - 1; i >= 0; --i) { |
| 1526 | resp <<= 8; |
| 1527 | resp |= resp_b[i] & 0xff; |
| 1528 | } |
| 1529 | } |
| 1530 | |
| 1531 | // process response |
| 1532 | if ((resp & accum->valid_mask) == accum->valid_value) { |
| 1533 | // valid sensor data; extract data field |
| 1534 | int32_t data = (int32_t)(resp >> accum->data_shift); |
| 1535 | data &= accum->data_max - 1; |
| 1536 | // 2s complement conversion if signed MSB is set |
| 1537 | if (accum->is_signed && (data & accum->data_msb_mask) != 0) |
| 1538 | data -= accum->data_max; |
| 1539 | // center offset |
| 1540 | data -= accum->center; |
| 1541 | // only accumulate if outside deadband |
| 1542 | if (data < -accum->deadband || data > accum->deadband) |
| 1543 | accum->value += data; |
| 1544 | ++accum->count; |
| 1545 | accum->last_value = data; |
| 1546 | } else { |
| 1547 | // no data from the sensor; just clear the last value |
| 1548 | accum->last_value = 0; |
| 1549 | } |
| 1550 | |
| 1551 | // reschedule timer |
| 1552 | accum->triggerTime += accum->period; |
| 1553 | // handle timer slip |
| 1554 | if (accum->triggerTime < currentTime) |
| 1555 | accum->triggerTime = currentTime + accum->period; |
| 1556 | int32_t status = 0; |
| 1557 | updateNotifierAlarm(accum->notifier, accum->triggerTime, &status); |
| 1558 | } |
| 1559 | |
| 1560 | /** |
| 1561 | * Initialize a SPI accumulator. |
| 1562 | * |
| 1563 | * @param port SPI port |
| 1564 | * @param period Time between reads, in us |
| 1565 | * @param cmd SPI command to send to request data |
| 1566 | * @param xfer_size SPI transfer size, in bytes |
| 1567 | * @param valid_mask Mask to apply to received data for validity checking |
| 1568 | * @param valid_data After valid_mask is applied, required matching value for |
| 1569 | * validity checking |
| 1570 | * @param data_shift Bit shift to apply to received data to get actual data |
| 1571 | * value |
| 1572 | * @param data_size Size (in bits) of data field |
| 1573 | * @param is_signed Is data field signed? |
| 1574 | * @param big_endian Is device big endian? |
| 1575 | */ |
| 1576 | void spiInitAccumulator(uint8_t port, uint32_t period, uint32_t cmd, |
| 1577 | uint8_t xfer_size, uint32_t valid_mask, uint32_t valid_value, |
| 1578 | uint8_t data_shift, uint8_t data_size, bool is_signed, |
| 1579 | bool big_endian, int32_t *status) { |
| 1580 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1581 | if (port > 4) return; |
| 1582 | if (!spiAccumulators[port]) |
| 1583 | spiAccumulators[port] = new SPIAccumulator(); |
| 1584 | SPIAccumulator* accum = spiAccumulators[port]; |
| 1585 | if (big_endian) { |
| 1586 | for (int i = xfer_size - 1; i >= 0; --i) { |
| 1587 | accum->cmd[i] = cmd & 0xff; |
| 1588 | cmd >>= 8; |
| 1589 | } |
| 1590 | } else { |
| 1591 | accum->cmd[0] = cmd & 0xff; cmd >>= 8; |
| 1592 | accum->cmd[1] = cmd & 0xff; cmd >>= 8; |
| 1593 | accum->cmd[2] = cmd & 0xff; cmd >>= 8; |
| 1594 | accum->cmd[3] = cmd & 0xff; |
| 1595 | } |
| 1596 | accum->period = period; |
| 1597 | accum->xfer_size = xfer_size; |
| 1598 | accum->valid_mask = valid_mask; |
| 1599 | accum->valid_value = valid_value; |
| 1600 | accum->data_shift = data_shift; |
| 1601 | accum->data_max = (1 << data_size); |
| 1602 | accum->data_msb_mask = (1 << (data_size - 1)); |
| 1603 | accum->is_signed = is_signed; |
| 1604 | accum->big_endian = big_endian; |
| 1605 | if (!accum->notifier) { |
| 1606 | accum->notifier = initializeNotifier(spiAccumulatorProcess, accum, status); |
| 1607 | accum->triggerTime = getFPGATime(status) + period; |
| 1608 | if (*status != 0) return; |
| 1609 | updateNotifierAlarm(accum->notifier, accum->triggerTime, status); |
| 1610 | } |
| 1611 | } |
| 1612 | |
| 1613 | /** |
| 1614 | * Frees a SPI accumulator. |
| 1615 | */ |
| 1616 | void spiFreeAccumulator(uint8_t port, int32_t *status) { |
| 1617 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1618 | SPIAccumulator* accum = spiAccumulators[port]; |
| 1619 | if (!accum) { |
| 1620 | *status = NULL_PARAMETER; |
| 1621 | return; |
| 1622 | } |
| 1623 | cleanNotifier(accum->notifier, status); |
| 1624 | delete accum; |
| 1625 | spiAccumulators[port] = nullptr; |
| 1626 | } |
| 1627 | |
| 1628 | /** |
| 1629 | * Resets the accumulator to zero. |
| 1630 | */ |
| 1631 | void spiResetAccumulator(uint8_t port, int32_t *status) { |
| 1632 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1633 | SPIAccumulator* accum = spiAccumulators[port]; |
| 1634 | if (!accum) { |
| 1635 | *status = NULL_PARAMETER; |
| 1636 | return; |
| 1637 | } |
| 1638 | accum->value = 0; |
| 1639 | accum->count = 0; |
| 1640 | accum->last_value = 0; |
| 1641 | } |
| 1642 | |
| 1643 | /** |
| 1644 | * Set the center value of the accumulator. |
| 1645 | * |
| 1646 | * The center value is subtracted from each value before it is added to the accumulator. This |
| 1647 | * is used for the center value of devices like gyros and accelerometers to make integration work |
| 1648 | * and to take the device offset into account when integrating. |
| 1649 | */ |
| 1650 | void spiSetAccumulatorCenter(uint8_t port, int32_t center, int32_t *status) { |
| 1651 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1652 | SPIAccumulator* accum = spiAccumulators[port]; |
| 1653 | if (!accum) { |
| 1654 | *status = NULL_PARAMETER; |
| 1655 | return; |
| 1656 | } |
| 1657 | accum->center = center; |
| 1658 | } |
| 1659 | |
| 1660 | /** |
| 1661 | * Set the accumulator's deadband. |
| 1662 | */ |
| 1663 | void spiSetAccumulatorDeadband(uint8_t port, int32_t deadband, int32_t *status) { |
| 1664 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1665 | SPIAccumulator* accum = spiAccumulators[port]; |
| 1666 | if (!accum) { |
| 1667 | *status = NULL_PARAMETER; |
| 1668 | return; |
| 1669 | } |
| 1670 | accum->deadband = deadband; |
| 1671 | } |
| 1672 | |
| 1673 | /** |
| 1674 | * Read the last value read by the accumulator engine. |
| 1675 | */ |
| 1676 | int32_t spiGetAccumulatorLastValue(uint8_t port, int32_t *status) { |
| 1677 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1678 | SPIAccumulator* accum = spiAccumulators[port]; |
| 1679 | if (!accum) { |
| 1680 | *status = NULL_PARAMETER; |
| 1681 | return 0; |
| 1682 | } |
| 1683 | return accum->last_value; |
| 1684 | } |
| 1685 | |
| 1686 | /** |
| 1687 | * Read the accumulated value. |
| 1688 | * |
| 1689 | * @return The 64-bit value accumulated since the last Reset(). |
| 1690 | */ |
| 1691 | int64_t spiGetAccumulatorValue(uint8_t port, int32_t *status) { |
| 1692 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1693 | SPIAccumulator* accum = spiAccumulators[port]; |
| 1694 | if (!accum) { |
| 1695 | *status = NULL_PARAMETER; |
| 1696 | return 0; |
| 1697 | } |
| 1698 | return accum->value; |
| 1699 | } |
| 1700 | |
| 1701 | /** |
| 1702 | * Read the number of accumulated values. |
| 1703 | * |
| 1704 | * Read the count of the accumulated values since the accumulator was last Reset(). |
| 1705 | * |
| 1706 | * @return The number of times samples from the channel were accumulated. |
| 1707 | */ |
| 1708 | uint32_t spiGetAccumulatorCount(uint8_t port, int32_t *status) { |
| 1709 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1710 | SPIAccumulator* accum = spiAccumulators[port]; |
| 1711 | if (!accum) { |
| 1712 | *status = NULL_PARAMETER; |
| 1713 | return 0; |
| 1714 | } |
| 1715 | return accum->count; |
| 1716 | } |
| 1717 | |
| 1718 | /** |
| 1719 | * Read the average of the accumulated value. |
| 1720 | * |
| 1721 | * @return The accumulated average value (value / count). |
| 1722 | */ |
| 1723 | double spiGetAccumulatorAverage(uint8_t port, int32_t *status) { |
| 1724 | int64_t value; |
| 1725 | uint32_t count; |
| 1726 | spiGetAccumulatorOutput(port, &value, &count, status); |
| 1727 | if (count == 0) return 0.0; |
| 1728 | return ((double)value) / count; |
| 1729 | } |
| 1730 | |
| 1731 | /** |
| 1732 | * Read the accumulated value and the number of accumulated values atomically. |
| 1733 | * |
| 1734 | * This function reads the value and count atomically. |
| 1735 | * This can be used for averaging. |
| 1736 | * |
| 1737 | * @param value Pointer to the 64-bit accumulated output. |
| 1738 | * @param count Pointer to the number of accumulation cycles. |
| 1739 | */ |
| 1740 | void spiGetAccumulatorOutput(uint8_t port, int64_t *value, uint32_t *count, |
| 1741 | int32_t *status) { |
| 1742 | std::lock_guard<priority_recursive_mutex> sync(spiGetSemaphore(port)); |
| 1743 | SPIAccumulator* accum = spiAccumulators[port]; |
| 1744 | if (!accum) { |
| 1745 | *status = NULL_PARAMETER; |
| 1746 | *value = 0; |
| 1747 | *count = 0; |
| 1748 | return; |
| 1749 | } |
| 1750 | *value = accum->value; |
| 1751 | *count = accum->count; |
| 1752 | } |
| 1753 | |
| 1754 | /* |
| 1755 | * Initialize the I2C port. Opens the port if necessary and saves the handle. |
| 1756 | * If opening the MXP port, also sets up the pin functions appropriately |
| 1757 | * @param port The port to open, 0 for the on-board, 1 for the MXP. |
| 1758 | */ |
| 1759 | void i2CInitialize(uint8_t port, int32_t *status) { |
| 1760 | initializeDigital(status); |
| 1761 | |
| 1762 | if(port > 1) |
| 1763 | { |
| 1764 | //Set port out of range error here |
| 1765 | return; |
| 1766 | } |
| 1767 | |
| 1768 | priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex; |
| 1769 | { |
| 1770 | std::lock_guard<priority_recursive_mutex> sync(lock); |
| 1771 | if(port == 0) { |
| 1772 | i2COnboardObjCount++; |
| 1773 | if (i2COnBoardHandle > 0) return; |
| 1774 | i2COnBoardHandle = i2clib_open("/dev/i2c-2"); |
| 1775 | } else if(port == 1) { |
| 1776 | i2CMXPObjCount++; |
| 1777 | if (i2CMXPHandle > 0) return; |
| 1778 | if(!allocateDIO(getPort(24), false, status)) return; |
| 1779 | if(!allocateDIO(getPort(25), false, status)) return; |
| 1780 | digitalSystem->writeEnableMXPSpecialFunction(digitalSystem->readEnableMXPSpecialFunction(status)|0xC000, status); |
| 1781 | i2CMXPHandle = i2clib_open("/dev/i2c-1"); |
| 1782 | } |
| 1783 | return; |
| 1784 | } |
| 1785 | } |
| 1786 | |
| 1787 | /** |
| 1788 | * Generic transaction. |
| 1789 | * |
| 1790 | * This is a lower-level interface to the I2C hardware giving you more control over each transaction. |
| 1791 | * |
| 1792 | * @param dataToSend Buffer of data to send as part of the transaction. |
| 1793 | * @param sendSize Number of bytes to send as part of the transaction. |
| 1794 | * @param dataReceived Buffer to read data into. |
| 1795 | * @param receiveSize Number of bytes to read from the device. |
| 1796 | * @return Transfer Aborted... false for success, true for aborted. |
| 1797 | */ |
| 1798 | int32_t i2CTransaction(uint8_t port, uint8_t deviceAddress, uint8_t *dataToSend, uint8_t sendSize, uint8_t *dataReceived, uint8_t receiveSize) |
| 1799 | { |
| 1800 | if(port > 1) { |
| 1801 | //Set port out of range error here |
| 1802 | return -1; |
| 1803 | } |
| 1804 | |
| 1805 | int32_t handle = port == 0 ? i2COnBoardHandle:i2CMXPHandle; |
| 1806 | priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex; |
| 1807 | |
| 1808 | { |
| 1809 | std::lock_guard<priority_recursive_mutex> sync(lock); |
| 1810 | return i2clib_writeread(handle, deviceAddress, (const char*) dataToSend, (int32_t) sendSize, (char*) dataReceived, (int32_t) receiveSize); |
| 1811 | } |
| 1812 | } |
| 1813 | |
| 1814 | /** |
| 1815 | * Execute a write transaction with the device. |
| 1816 | * |
| 1817 | * Write a single byte to a register on a device and wait until the |
| 1818 | * transaction is complete. |
| 1819 | * |
| 1820 | * @param registerAddress The address of the register on the device to be written. |
| 1821 | * @param data The byte to write to the register on the device. |
| 1822 | * @return Transfer Aborted... false for success, true for aborted. |
| 1823 | */ |
| 1824 | int32_t i2CWrite(uint8_t port, uint8_t deviceAddress, uint8_t* dataToSend, uint8_t sendSize) |
| 1825 | { |
| 1826 | if(port > 1) { |
| 1827 | //Set port out of range error here |
| 1828 | return -1; |
| 1829 | } |
| 1830 | |
| 1831 | int32_t handle = port == 0 ? i2COnBoardHandle:i2CMXPHandle; |
| 1832 | priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex; |
| 1833 | { |
| 1834 | std::lock_guard<priority_recursive_mutex> sync(lock); |
| 1835 | return i2clib_write(handle, deviceAddress, (const char*) dataToSend, (int32_t) sendSize); |
| 1836 | } |
| 1837 | } |
| 1838 | |
| 1839 | /** |
| 1840 | * Execute a read transaction with the device. |
| 1841 | * |
| 1842 | * Read bytes from a device. |
| 1843 | * Most I2C devices will auto-increment the register pointer internally allowing |
| 1844 | * you to read consecutive registers on a device in a single transaction. |
| 1845 | * |
| 1846 | * @param registerAddress The register to read first in the transaction. |
| 1847 | * @param count The number of bytes to read in the transaction. |
| 1848 | * @param buffer A pointer to the array of bytes to store the data read from the device. |
| 1849 | * @return Transfer Aborted... false for success, true for aborted. |
| 1850 | */ |
| 1851 | int32_t i2CRead(uint8_t port, uint8_t deviceAddress, uint8_t *buffer, uint8_t count) |
| 1852 | { |
| 1853 | if(port > 1) { |
| 1854 | //Set port out of range error here |
| 1855 | return -1; |
| 1856 | } |
| 1857 | |
| 1858 | int32_t handle = port == 0 ? i2COnBoardHandle:i2CMXPHandle; |
| 1859 | priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex; |
| 1860 | { |
| 1861 | std::lock_guard<priority_recursive_mutex> sync(lock); |
| 1862 | return i2clib_read(handle, deviceAddress, (char*) buffer, (int32_t) count); |
| 1863 | } |
| 1864 | |
| 1865 | } |
| 1866 | |
| 1867 | void i2CClose(uint8_t port) { |
| 1868 | if(port > 1) { |
| 1869 | //Set port out of range error here |
| 1870 | return; |
| 1871 | } |
| 1872 | priority_recursive_mutex &lock = port == 0 ? digitalI2COnBoardMutex : digitalI2CMXPMutex; |
| 1873 | { |
| 1874 | std::lock_guard<priority_recursive_mutex> sync(lock); |
| 1875 | if((port == 0 ? i2COnboardObjCount--:i2CMXPObjCount--) == 0) { |
| 1876 | int32_t handle = port == 0 ? i2COnBoardHandle:i2CMXPHandle; |
| 1877 | i2clib_close(handle); |
| 1878 | } |
| 1879 | } |
| 1880 | return; |
| 1881 | } |
Brian Silverman | 1a67511 | 2016-02-20 20:42:49 -0500 | [diff] [blame^] | 1882 | |
| 1883 | } // extern "C" |