Brian Silverman | f7f267a | 2017-02-04 16:16:08 -0800 | [diff] [blame^] | 1 | /*----------------------------------------------------------------------------*/ |
| 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/SPI.h" |
| 9 | |
| 10 | #include <atomic> |
| 11 | #include <cstdio> |
| 12 | |
| 13 | #include "DigitalInternal.h" |
| 14 | #include "HAL/DIO.h" |
| 15 | #include "HAL/HAL.h" |
| 16 | #include "HAL/Notifier.h" |
| 17 | #include "HAL/cpp/make_unique.h" |
| 18 | #include "HAL/cpp/priority_mutex.h" |
| 19 | #include "HAL/handles/HandlesInternal.h" |
| 20 | #include "spilib/spi-lib.h" |
| 21 | |
| 22 | using namespace hal; |
| 23 | |
| 24 | static int32_t m_spiCS0Handle = 0; |
| 25 | static int32_t m_spiCS1Handle = 0; |
| 26 | static int32_t m_spiCS2Handle = 0; |
| 27 | static int32_t m_spiCS3Handle = 0; |
| 28 | static int32_t m_spiMXPHandle = 0; |
| 29 | static priority_recursive_mutex spiOnboardMutex; |
| 30 | static priority_recursive_mutex spiMXPMutex; |
| 31 | |
| 32 | // MXP SPI does not count towards this |
| 33 | std::atomic<int32_t> spiPortCount{0}; |
| 34 | |
| 35 | static HAL_DigitalHandle digitalHandles[9]{HAL_kInvalidHandle}; |
| 36 | |
| 37 | /** |
| 38 | * Get the semaphore for a SPI port |
| 39 | * |
| 40 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 41 | * @return The semaphore for the SPI port. |
| 42 | */ |
| 43 | static priority_recursive_mutex& spiGetMutex(int32_t port) { |
| 44 | if (port < 4) |
| 45 | return spiOnboardMutex; |
| 46 | else |
| 47 | return spiMXPMutex; |
| 48 | } |
| 49 | |
| 50 | extern "C" { |
| 51 | |
| 52 | struct SPIAccumulator { |
| 53 | std::atomic<HAL_NotifierHandle> notifier{0}; |
| 54 | uint64_t triggerTime; |
| 55 | int32_t period; |
| 56 | |
| 57 | int64_t value = 0; |
| 58 | uint32_t count = 0; |
| 59 | int32_t lastValue = 0; |
| 60 | |
| 61 | int32_t center = 0; |
| 62 | int32_t deadband = 0; |
| 63 | |
| 64 | uint8_t cmd[4]; // command to send (up to 4 bytes) |
| 65 | int32_t validMask; |
| 66 | int32_t validValue; |
| 67 | int32_t dataMax; // one more than max data value |
| 68 | int32_t dataMsbMask; // data field MSB mask (for signed) |
| 69 | uint8_t dataShift; // data field shift right amount, in bits |
| 70 | uint8_t xferSize; // SPI transfer size, in bytes (up to 4) |
| 71 | uint8_t port; |
| 72 | bool isSigned; // is data field signed? |
| 73 | bool bigEndian; // is response big endian? |
| 74 | }; |
| 75 | std::unique_ptr<SPIAccumulator> spiAccumulators[5]; |
| 76 | |
| 77 | static void CommonSPIPortInit(int32_t* status) { |
| 78 | // All false cases will set |
| 79 | if (spiPortCount.fetch_add(1) == 0) { |
| 80 | // Have not been initialized yet |
| 81 | initializeDigital(status); |
| 82 | if (*status != 0) return; |
| 83 | // MISO |
| 84 | if ((digitalHandles[3] = HAL_InitializeDIOPort(createPortHandleForSPI(29), |
| 85 | false, status)) == |
| 86 | HAL_kInvalidHandle) { |
| 87 | std::printf("Failed to allocate DIO 29 (MISO)\n"); |
| 88 | return; |
| 89 | } |
| 90 | // MOSI |
| 91 | if ((digitalHandles[4] = HAL_InitializeDIOPort(createPortHandleForSPI(30), |
| 92 | false, status)) == |
| 93 | HAL_kInvalidHandle) { |
| 94 | std::printf("Failed to allocate DIO 30 (MOSI)\n"); |
| 95 | HAL_FreeDIOPort(digitalHandles[3]); // free the first port allocated |
| 96 | return; |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | static void CommonSPIPortFree() { |
| 102 | if (spiPortCount.fetch_sub(1) == 1) { |
| 103 | // Clean up SPI Handles |
| 104 | HAL_FreeDIOPort(digitalHandles[3]); |
| 105 | HAL_FreeDIOPort(digitalHandles[4]); |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | /* |
| 110 | * Initialize the spi port. Opens the port if necessary and saves the handle. |
| 111 | * If opening the MXP port, also sets up the channel functions appropriately |
| 112 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS3, 4 for MXP |
| 113 | */ |
| 114 | void HAL_InitializeSPI(int32_t port, int32_t* status) { |
| 115 | if (HAL_GetSPIHandle(port) != 0) return; |
| 116 | switch (port) { |
| 117 | case 0: |
| 118 | CommonSPIPortInit(status); |
| 119 | if (*status != 0) return; |
| 120 | // CS0 is not a DIO port, so nothing to allocate |
| 121 | HAL_SetSPIHandle(0, spilib_open("/dev/spidev0.0")); |
| 122 | break; |
| 123 | case 1: |
| 124 | CommonSPIPortInit(status); |
| 125 | if (*status != 0) return; |
| 126 | // CS1, Allocate |
| 127 | if ((digitalHandles[0] = HAL_InitializeDIOPort( |
| 128 | HAL_GetPort(26), false, status)) == HAL_kInvalidHandle) { |
| 129 | std::printf("Failed to allocate DIO 26 (CS1)\n"); |
| 130 | CommonSPIPortFree(); |
| 131 | return; |
| 132 | } |
| 133 | HAL_SetSPIHandle(1, spilib_open("/dev/spidev0.1")); |
| 134 | break; |
| 135 | case 2: |
| 136 | CommonSPIPortInit(status); |
| 137 | if (*status != 0) return; |
| 138 | // CS2, Allocate |
| 139 | if ((digitalHandles[1] = HAL_InitializeDIOPort( |
| 140 | HAL_GetPort(27), false, status)) == HAL_kInvalidHandle) { |
| 141 | std::printf("Failed to allocate DIO 27 (CS2)\n"); |
| 142 | CommonSPIPortFree(); |
| 143 | return; |
| 144 | } |
| 145 | HAL_SetSPIHandle(2, spilib_open("/dev/spidev0.2")); |
| 146 | break; |
| 147 | case 3: |
| 148 | CommonSPIPortInit(status); |
| 149 | if (*status != 0) return; |
| 150 | // CS3, Allocate |
| 151 | if ((digitalHandles[2] = HAL_InitializeDIOPort( |
| 152 | HAL_GetPort(28), false, status)) == HAL_kInvalidHandle) { |
| 153 | std::printf("Failed to allocate DIO 28 (CS3)\n"); |
| 154 | CommonSPIPortFree(); |
| 155 | return; |
| 156 | } |
| 157 | HAL_SetSPIHandle(3, spilib_open("/dev/spidev0.3")); |
| 158 | break; |
| 159 | case 4: |
| 160 | initializeDigital(status); |
| 161 | if (*status != 0) return; |
| 162 | if ((digitalHandles[5] = HAL_InitializeDIOPort( |
| 163 | HAL_GetPort(14), false, status)) == HAL_kInvalidHandle) { |
| 164 | std::printf("Failed to allocate DIO 14\n"); |
| 165 | return; |
| 166 | } |
| 167 | if ((digitalHandles[6] = HAL_InitializeDIOPort( |
| 168 | HAL_GetPort(15), false, status)) == HAL_kInvalidHandle) { |
| 169 | std::printf("Failed to allocate DIO 15\n"); |
| 170 | HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated |
| 171 | return; |
| 172 | } |
| 173 | if ((digitalHandles[7] = HAL_InitializeDIOPort( |
| 174 | HAL_GetPort(16), false, status)) == HAL_kInvalidHandle) { |
| 175 | std::printf("Failed to allocate DIO 16\n"); |
| 176 | HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated |
| 177 | HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated |
| 178 | return; |
| 179 | } |
| 180 | if ((digitalHandles[8] = HAL_InitializeDIOPort( |
| 181 | HAL_GetPort(17), false, status)) == HAL_kInvalidHandle) { |
| 182 | std::printf("Failed to allocate DIO 17\n"); |
| 183 | HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated |
| 184 | HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated |
| 185 | HAL_FreeDIOPort(digitalHandles[7]); // free the third port allocated |
| 186 | return; |
| 187 | } |
| 188 | digitalSystem->writeEnableMXPSpecialFunction( |
| 189 | digitalSystem->readEnableMXPSpecialFunction(status) | 0x00F0, status); |
| 190 | HAL_SetSPIHandle(4, spilib_open("/dev/spidev1.0")); |
| 191 | break; |
| 192 | default: |
| 193 | *status = PARAMETER_OUT_OF_RANGE; |
| 194 | break; |
| 195 | } |
| 196 | return; |
| 197 | } |
| 198 | |
| 199 | /** |
| 200 | * Generic transaction. |
| 201 | * |
| 202 | * This is a lower-level interface to the spi hardware giving you more control |
| 203 | * over each transaction. |
| 204 | * |
| 205 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 206 | * @param dataToSend Buffer of data to send as part of the transaction. |
| 207 | * @param dataReceived Buffer to read data into. |
| 208 | * @param size Number of bytes to transfer. [0..7] |
| 209 | * @return Number of bytes transferred, -1 for error |
| 210 | */ |
| 211 | int32_t HAL_TransactionSPI(int32_t port, uint8_t* dataToSend, |
| 212 | uint8_t* dataReceived, int32_t size) { |
| 213 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 214 | return spilib_writeread( |
| 215 | HAL_GetSPIHandle(port), reinterpret_cast<const char*>(dataToSend), |
| 216 | reinterpret_cast<char*>(dataReceived), static_cast<int32_t>(size)); |
| 217 | } |
| 218 | |
| 219 | /** |
| 220 | * Execute a write transaction with the device. |
| 221 | * |
| 222 | * Write to a device and wait until the transaction is complete. |
| 223 | * |
| 224 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 225 | * @param datToSend The data to write to the register on the device. |
| 226 | * @param sendSize The number of bytes to be written |
| 227 | * @return The number of bytes written. -1 for an error |
| 228 | */ |
| 229 | int32_t HAL_WriteSPI(int32_t port, uint8_t* dataToSend, int32_t sendSize) { |
| 230 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 231 | return spilib_write(HAL_GetSPIHandle(port), |
| 232 | reinterpret_cast<const char*>(dataToSend), |
| 233 | static_cast<int32_t>(sendSize)); |
| 234 | } |
| 235 | |
| 236 | /** |
| 237 | * Execute a read from the device. |
| 238 | * |
| 239 | * This method does not write any data out to the device |
| 240 | * Most spi devices will require a register address to be written before |
| 241 | * they begin returning data |
| 242 | * |
| 243 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 244 | * @param buffer A pointer to the array of bytes to store the data read from the |
| 245 | * device. |
| 246 | * @param count The number of bytes to read in the transaction. [1..7] |
| 247 | * @return Number of bytes read. -1 for error. |
| 248 | */ |
| 249 | int32_t HAL_ReadSPI(int32_t port, uint8_t* buffer, int32_t count) { |
| 250 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 251 | return spilib_read(HAL_GetSPIHandle(port), reinterpret_cast<char*>(buffer), |
| 252 | static_cast<int32_t>(count)); |
| 253 | } |
| 254 | |
| 255 | /** |
| 256 | * Close the SPI port |
| 257 | * |
| 258 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 259 | */ |
| 260 | void HAL_CloseSPI(int32_t port) { |
| 261 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 262 | if (spiAccumulators[port]) { |
| 263 | int32_t status = 0; |
| 264 | HAL_FreeSPIAccumulator(port, &status); |
| 265 | } |
| 266 | spilib_close(HAL_GetSPIHandle(port)); |
| 267 | HAL_SetSPIHandle(port, 0); |
| 268 | if (port < 4) { |
| 269 | CommonSPIPortFree(); |
| 270 | } |
| 271 | switch (port) { |
| 272 | // Case 0 does not need to do anything |
| 273 | case 1: |
| 274 | HAL_FreeDIOPort(digitalHandles[0]); |
| 275 | break; |
| 276 | case 2: |
| 277 | HAL_FreeDIOPort(digitalHandles[1]); |
| 278 | break; |
| 279 | case 3: |
| 280 | HAL_FreeDIOPort(digitalHandles[2]); |
| 281 | break; |
| 282 | case 4: |
| 283 | HAL_FreeDIOPort(digitalHandles[5]); |
| 284 | HAL_FreeDIOPort(digitalHandles[6]); |
| 285 | HAL_FreeDIOPort(digitalHandles[7]); |
| 286 | HAL_FreeDIOPort(digitalHandles[8]); |
| 287 | break; |
| 288 | default: |
| 289 | break; |
| 290 | } |
| 291 | return; |
| 292 | } |
| 293 | |
| 294 | /** |
| 295 | * Set the clock speed for the SPI bus. |
| 296 | * |
| 297 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 298 | * @param speed The speed in Hz (0-1MHz) |
| 299 | */ |
| 300 | void HAL_SetSPISpeed(int32_t port, int32_t speed) { |
| 301 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 302 | spilib_setspeed(HAL_GetSPIHandle(port), speed); |
| 303 | } |
| 304 | |
| 305 | /** |
| 306 | * Set the SPI options |
| 307 | * |
| 308 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 309 | * @param msbFirst True to write the MSB first, False for LSB first |
| 310 | * @param sampleOnTrailing True to sample on the trailing edge, False to sample |
| 311 | * on the leading edge |
| 312 | * @param clkIdleHigh True to set the clock to active low, False to set the |
| 313 | * clock active high |
| 314 | */ |
| 315 | void HAL_SetSPIOpts(int32_t port, HAL_Bool msbFirst, HAL_Bool sampleOnTrailing, |
| 316 | HAL_Bool clkIdleHigh) { |
| 317 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 318 | spilib_setopts(HAL_GetSPIHandle(port), msbFirst, sampleOnTrailing, |
| 319 | clkIdleHigh); |
| 320 | } |
| 321 | |
| 322 | /** |
| 323 | * Set the CS Active high for a SPI port |
| 324 | * |
| 325 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 326 | */ |
| 327 | void HAL_SetSPIChipSelectActiveHigh(int32_t port, int32_t* status) { |
| 328 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 329 | if (port < 4) { |
| 330 | spiSystem->writeChipSelectActiveHigh_Hdr( |
| 331 | spiSystem->readChipSelectActiveHigh_Hdr(status) | (1 << port), status); |
| 332 | } else { |
| 333 | spiSystem->writeChipSelectActiveHigh_MXP(1, status); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | /** |
| 338 | * Set the CS Active low for a SPI port |
| 339 | * |
| 340 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 341 | */ |
| 342 | void HAL_SetSPIChipSelectActiveLow(int32_t port, int32_t* status) { |
| 343 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 344 | if (port < 4) { |
| 345 | spiSystem->writeChipSelectActiveHigh_Hdr( |
| 346 | spiSystem->readChipSelectActiveHigh_Hdr(status) & ~(1 << port), status); |
| 347 | } else { |
| 348 | spiSystem->writeChipSelectActiveHigh_MXP(0, status); |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | /** |
| 353 | * Get the stored handle for a SPI port |
| 354 | * |
| 355 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP |
| 356 | * @return The stored handle for the SPI port. 0 represents no stored handle. |
| 357 | */ |
| 358 | int32_t HAL_GetSPIHandle(int32_t port) { |
| 359 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 360 | switch (port) { |
| 361 | case 0: |
| 362 | return m_spiCS0Handle; |
| 363 | case 1: |
| 364 | return m_spiCS1Handle; |
| 365 | case 2: |
| 366 | return m_spiCS2Handle; |
| 367 | case 3: |
| 368 | return m_spiCS3Handle; |
| 369 | case 4: |
| 370 | return m_spiMXPHandle; |
| 371 | default: |
| 372 | return 0; |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | /** |
| 377 | * Set the stored handle for a SPI port |
| 378 | * |
| 379 | * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for |
| 380 | * MXP. |
| 381 | * @param handle The value of the handle for the port. |
| 382 | */ |
| 383 | void HAL_SetSPIHandle(int32_t port, int32_t handle) { |
| 384 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 385 | switch (port) { |
| 386 | case 0: |
| 387 | m_spiCS0Handle = handle; |
| 388 | break; |
| 389 | case 1: |
| 390 | m_spiCS1Handle = handle; |
| 391 | break; |
| 392 | case 2: |
| 393 | m_spiCS2Handle = handle; |
| 394 | break; |
| 395 | case 3: |
| 396 | m_spiCS3Handle = handle; |
| 397 | break; |
| 398 | case 4: |
| 399 | m_spiMXPHandle = handle; |
| 400 | break; |
| 401 | default: |
| 402 | break; |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | static void spiAccumulatorProcess(uint64_t currentTime, |
| 407 | HAL_NotifierHandle handle) { |
| 408 | int32_t status = 0; |
| 409 | auto param = HAL_GetNotifierParam(handle, &status); |
| 410 | if (param == nullptr) return; |
| 411 | SPIAccumulator* accum = static_cast<SPIAccumulator*>(param); |
| 412 | |
| 413 | // perform SPI transaction |
| 414 | uint8_t resp_b[4]; |
| 415 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(accum->port)); |
| 416 | spilib_writeread( |
| 417 | HAL_GetSPIHandle(accum->port), reinterpret_cast<const char*>(accum->cmd), |
| 418 | reinterpret_cast<char*>(resp_b), static_cast<int32_t>(accum->xferSize)); |
| 419 | |
| 420 | // convert from bytes |
| 421 | uint32_t resp = 0; |
| 422 | if (accum->bigEndian) { |
| 423 | for (int32_t i = 0; i < accum->xferSize; ++i) { |
| 424 | resp <<= 8; |
| 425 | resp |= resp_b[i] & 0xff; |
| 426 | } |
| 427 | } else { |
| 428 | for (int32_t i = accum->xferSize - 1; i >= 0; --i) { |
| 429 | resp <<= 8; |
| 430 | resp |= resp_b[i] & 0xff; |
| 431 | } |
| 432 | } |
| 433 | |
| 434 | // process response |
| 435 | if ((resp & accum->validMask) == static_cast<uint32_t>(accum->validValue)) { |
| 436 | // valid sensor data; extract data field |
| 437 | int32_t data = static_cast<int32_t>(resp >> accum->dataShift); |
| 438 | data &= accum->dataMax - 1; |
| 439 | // 2s complement conversion if signed MSB is set |
| 440 | if (accum->isSigned && (data & accum->dataMsbMask) != 0) |
| 441 | data -= accum->dataMax; |
| 442 | // center offset |
| 443 | data -= accum->center; |
| 444 | // only accumulate if outside deadband |
| 445 | if (data < -accum->deadband || data > accum->deadband) accum->value += data; |
| 446 | ++accum->count; |
| 447 | accum->lastValue = data; |
| 448 | } else { |
| 449 | // no data from the sensor; just clear the last value |
| 450 | accum->lastValue = 0; |
| 451 | } |
| 452 | |
| 453 | // reschedule timer |
| 454 | accum->triggerTime += accum->period; |
| 455 | // handle timer slip |
| 456 | if (accum->triggerTime < currentTime) |
| 457 | accum->triggerTime = currentTime + accum->period; |
| 458 | status = 0; |
| 459 | HAL_UpdateNotifierAlarm(accum->notifier, accum->triggerTime, &status); |
| 460 | } |
| 461 | |
| 462 | /** |
| 463 | * Initialize a SPI accumulator. |
| 464 | * |
| 465 | * @param port SPI port |
| 466 | * @param period Time between reads, in us |
| 467 | * @param cmd SPI command to send to request data |
| 468 | * @param xferSize SPI transfer size, in bytes |
| 469 | * @param validMask Mask to apply to received data for validity checking |
| 470 | * @param valid_data After validMask is applied, required matching value for |
| 471 | * validity checking |
| 472 | * @param dataShift Bit shift to apply to received data to get actual data |
| 473 | * value |
| 474 | * @param dataSize Size (in bits) of data field |
| 475 | * @param isSigned Is data field signed? |
| 476 | * @param bigEndian Is device big endian? |
| 477 | */ |
| 478 | void HAL_InitSPIAccumulator(int32_t port, int32_t period, int32_t cmd, |
| 479 | int32_t xferSize, int32_t validMask, |
| 480 | int32_t validValue, int32_t dataShift, |
| 481 | int32_t dataSize, HAL_Bool isSigned, |
| 482 | HAL_Bool bigEndian, int32_t* status) { |
| 483 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 484 | if (port > 4) return; |
| 485 | if (!spiAccumulators[port]) |
| 486 | spiAccumulators[port] = std::make_unique<SPIAccumulator>(); |
| 487 | SPIAccumulator* accum = spiAccumulators[port].get(); |
| 488 | if (bigEndian) { |
| 489 | for (int32_t i = xferSize - 1; i >= 0; --i) { |
| 490 | accum->cmd[i] = cmd & 0xff; |
| 491 | cmd >>= 8; |
| 492 | } |
| 493 | } else { |
| 494 | accum->cmd[0] = cmd & 0xff; |
| 495 | cmd >>= 8; |
| 496 | accum->cmd[1] = cmd & 0xff; |
| 497 | cmd >>= 8; |
| 498 | accum->cmd[2] = cmd & 0xff; |
| 499 | cmd >>= 8; |
| 500 | accum->cmd[3] = cmd & 0xff; |
| 501 | } |
| 502 | accum->period = period; |
| 503 | accum->xferSize = xferSize; |
| 504 | accum->validMask = validMask; |
| 505 | accum->validValue = validValue; |
| 506 | accum->dataShift = dataShift; |
| 507 | accum->dataMax = (1 << dataSize); |
| 508 | accum->dataMsbMask = (1 << (dataSize - 1)); |
| 509 | accum->isSigned = isSigned; |
| 510 | accum->bigEndian = bigEndian; |
| 511 | if (!accum->notifier) { |
| 512 | accum->notifier = |
| 513 | HAL_InitializeNotifier(spiAccumulatorProcess, accum, status); |
| 514 | accum->triggerTime = HAL_GetFPGATime(status) + period; |
| 515 | if (*status != 0) return; |
| 516 | HAL_UpdateNotifierAlarm(accum->notifier, accum->triggerTime, status); |
| 517 | } |
| 518 | } |
| 519 | |
| 520 | /** |
| 521 | * Frees a SPI accumulator. |
| 522 | */ |
| 523 | void HAL_FreeSPIAccumulator(int32_t port, int32_t* status) { |
| 524 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 525 | SPIAccumulator* accum = spiAccumulators[port].get(); |
| 526 | if (!accum) { |
| 527 | *status = NULL_PARAMETER; |
| 528 | return; |
| 529 | } |
| 530 | HAL_NotifierHandle handle = accum->notifier.exchange(0); |
| 531 | HAL_CleanNotifier(handle, status); |
| 532 | spiAccumulators[port] = nullptr; |
| 533 | } |
| 534 | |
| 535 | /** |
| 536 | * Resets the accumulator to zero. |
| 537 | */ |
| 538 | void HAL_ResetSPIAccumulator(int32_t port, int32_t* status) { |
| 539 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 540 | SPIAccumulator* accum = spiAccumulators[port].get(); |
| 541 | if (!accum) { |
| 542 | *status = NULL_PARAMETER; |
| 543 | return; |
| 544 | } |
| 545 | accum->value = 0; |
| 546 | accum->count = 0; |
| 547 | accum->lastValue = 0; |
| 548 | } |
| 549 | |
| 550 | /** |
| 551 | * Set the center value of the accumulator. |
| 552 | * |
| 553 | * The center value is subtracted from each value before it is added to the |
| 554 | * accumulator. This |
| 555 | * is used for the center value of devices like gyros and accelerometers to make |
| 556 | * integration work |
| 557 | * and to take the device offset into account when integrating. |
| 558 | */ |
| 559 | void HAL_SetSPIAccumulatorCenter(int32_t port, int32_t center, |
| 560 | int32_t* status) { |
| 561 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 562 | SPIAccumulator* accum = spiAccumulators[port].get(); |
| 563 | if (!accum) { |
| 564 | *status = NULL_PARAMETER; |
| 565 | return; |
| 566 | } |
| 567 | accum->center = center; |
| 568 | } |
| 569 | |
| 570 | /** |
| 571 | * Set the accumulator's deadband. |
| 572 | */ |
| 573 | void HAL_SetSPIAccumulatorDeadband(int32_t port, int32_t deadband, |
| 574 | int32_t* status) { |
| 575 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 576 | SPIAccumulator* accum = spiAccumulators[port].get(); |
| 577 | if (!accum) { |
| 578 | *status = NULL_PARAMETER; |
| 579 | return; |
| 580 | } |
| 581 | accum->deadband = deadband; |
| 582 | } |
| 583 | |
| 584 | /** |
| 585 | * Read the last value read by the accumulator engine. |
| 586 | */ |
| 587 | int32_t HAL_GetSPIAccumulatorLastValue(int32_t port, int32_t* status) { |
| 588 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 589 | SPIAccumulator* accum = spiAccumulators[port].get(); |
| 590 | if (!accum) { |
| 591 | *status = NULL_PARAMETER; |
| 592 | return 0; |
| 593 | } |
| 594 | return accum->lastValue; |
| 595 | } |
| 596 | |
| 597 | /** |
| 598 | * Read the accumulated value. |
| 599 | * |
| 600 | * @return The 64-bit value accumulated since the last Reset(). |
| 601 | */ |
| 602 | int64_t HAL_GetSPIAccumulatorValue(int32_t port, int32_t* status) { |
| 603 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 604 | SPIAccumulator* accum = spiAccumulators[port].get(); |
| 605 | if (!accum) { |
| 606 | *status = NULL_PARAMETER; |
| 607 | return 0; |
| 608 | } |
| 609 | return accum->value; |
| 610 | } |
| 611 | |
| 612 | /** |
| 613 | * Read the number of accumulated values. |
| 614 | * |
| 615 | * Read the count of the accumulated values since the accumulator was last |
| 616 | * Reset(). |
| 617 | * |
| 618 | * @return The number of times samples from the channel were accumulated. |
| 619 | */ |
| 620 | int64_t HAL_GetSPIAccumulatorCount(int32_t port, int32_t* status) { |
| 621 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 622 | SPIAccumulator* accum = spiAccumulators[port].get(); |
| 623 | if (!accum) { |
| 624 | *status = NULL_PARAMETER; |
| 625 | return 0; |
| 626 | } |
| 627 | return accum->count; |
| 628 | } |
| 629 | |
| 630 | /** |
| 631 | * Read the average of the accumulated value. |
| 632 | * |
| 633 | * @return The accumulated average value (value / count). |
| 634 | */ |
| 635 | double HAL_GetSPIAccumulatorAverage(int32_t port, int32_t* status) { |
| 636 | int64_t value; |
| 637 | int64_t count; |
| 638 | HAL_GetSPIAccumulatorOutput(port, &value, &count, status); |
| 639 | if (count == 0) return 0.0; |
| 640 | return static_cast<double>(value) / count; |
| 641 | } |
| 642 | |
| 643 | /** |
| 644 | * Read the accumulated value and the number of accumulated values atomically. |
| 645 | * |
| 646 | * This function reads the value and count atomically. |
| 647 | * This can be used for averaging. |
| 648 | * |
| 649 | * @param value Pointer to the 64-bit accumulated output. |
| 650 | * @param count Pointer to the number of accumulation cycles. |
| 651 | */ |
| 652 | void HAL_GetSPIAccumulatorOutput(int32_t port, int64_t* value, int64_t* count, |
| 653 | int32_t* status) { |
| 654 | std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port)); |
| 655 | SPIAccumulator* accum = spiAccumulators[port].get(); |
| 656 | if (!accum) { |
| 657 | *status = NULL_PARAMETER; |
| 658 | *value = 0; |
| 659 | *count = 0; |
| 660 | return; |
| 661 | } |
| 662 | *value = accum->value; |
| 663 | *count = accum->count; |
| 664 | } |
| 665 | } |