| /*----------------------------------------------------------------------------*/ |
| /* Copyright (c) FIRST 2016-2017. All Rights Reserved. */ |
| /* Open Source Software - may be modified and shared by FRC teams. The code */ |
| /* must be accompanied by the FIRST BSD license file in the root directory of */ |
| /* the project. */ |
| /*----------------------------------------------------------------------------*/ |
| |
| #include "HAL/Accelerometer.h" |
| |
| #include <stdint.h> |
| |
| #include <cassert> |
| #include <cstdio> |
| #include <memory> |
| |
| #include "HAL/ChipObject.h" |
| #include "HAL/HAL.h" |
| |
| using namespace hal; |
| |
| // The 7-bit I2C address with a 0 "send" bit |
| static const uint8_t kSendAddress = (0x1c << 1) | 0; |
| |
| // The 7-bit I2C address with a 1 "receive" bit |
| static const uint8_t kReceiveAddress = (0x1c << 1) | 1; |
| |
| static const uint8_t kControlTxRx = 1; |
| static const uint8_t kControlStart = 2; |
| static const uint8_t kControlStop = 4; |
| |
| static std::unique_ptr<tAccel> accel; |
| static HAL_AccelerometerRange accelerometerRange; |
| |
| // Register addresses |
| enum Register { |
| kReg_Status = 0x00, |
| kReg_OutXMSB = 0x01, |
| kReg_OutXLSB = 0x02, |
| kReg_OutYMSB = 0x03, |
| kReg_OutYLSB = 0x04, |
| kReg_OutZMSB = 0x05, |
| kReg_OutZLSB = 0x06, |
| kReg_Sysmod = 0x0B, |
| kReg_IntSource = 0x0C, |
| kReg_WhoAmI = 0x0D, |
| kReg_XYZDataCfg = 0x0E, |
| kReg_HPFilterCutoff = 0x0F, |
| kReg_PLStatus = 0x10, |
| kReg_PLCfg = 0x11, |
| kReg_PLCount = 0x12, |
| kReg_PLBfZcomp = 0x13, |
| kReg_PLThsReg = 0x14, |
| kReg_FFMtCfg = 0x15, |
| kReg_FFMtSrc = 0x16, |
| kReg_FFMtThs = 0x17, |
| kReg_FFMtCount = 0x18, |
| kReg_TransientCfg = 0x1D, |
| kReg_TransientSrc = 0x1E, |
| kReg_TransientThs = 0x1F, |
| kReg_TransientCount = 0x20, |
| kReg_PulseCfg = 0x21, |
| kReg_PulseSrc = 0x22, |
| kReg_PulseThsx = 0x23, |
| kReg_PulseThsy = 0x24, |
| kReg_PulseThsz = 0x25, |
| kReg_PulseTmlt = 0x26, |
| kReg_PulseLtcy = 0x27, |
| kReg_PulseWind = 0x28, |
| kReg_ASlpCount = 0x29, |
| kReg_CtrlReg1 = 0x2A, |
| kReg_CtrlReg2 = 0x2B, |
| kReg_CtrlReg3 = 0x2C, |
| kReg_CtrlReg4 = 0x2D, |
| kReg_CtrlReg5 = 0x2E, |
| kReg_OffX = 0x2F, |
| kReg_OffY = 0x30, |
| kReg_OffZ = 0x31 |
| }; |
| |
| static void writeRegister(Register reg, uint8_t data); |
| static uint8_t readRegister(Register reg); |
| |
| /** |
| * Initialize the accelerometer. |
| */ |
| static void initializeAccelerometer() { |
| int32_t status; |
| |
| if (!accel) { |
| accel.reset(tAccel::create(&status)); |
| |
| // Enable I2C |
| accel->writeCNFG(1, &status); |
| |
| // Set the counter to 100 kbps |
| accel->writeCNTR(213, &status); |
| |
| // The device identification number should be 0x2a |
| assert(readRegister(kReg_WhoAmI) == 0x2a); |
| } |
| } |
| |
| static void writeRegister(Register reg, uint8_t data) { |
| int32_t status = 0; |
| uint64_t initialTime; |
| |
| accel->writeADDR(kSendAddress, &status); |
| |
| // Send a start transmit/receive message with the register address |
| accel->writeCNTL(kControlStart | kControlTxRx, &status); |
| accel->writeDATO(reg, &status); |
| accel->strobeGO(&status); |
| |
| // Execute and wait until it's done (up to a millisecond) |
| initialTime = HAL_GetFPGATime(&status); |
| while (accel->readSTAT(&status) & 1) { |
| if (HAL_GetFPGATime(&status) > initialTime + 1000) break; |
| } |
| |
| // Send a stop transmit/receive message with the data |
| accel->writeCNTL(kControlStop | kControlTxRx, &status); |
| accel->writeDATO(data, &status); |
| accel->strobeGO(&status); |
| |
| // Execute and wait until it's done (up to a millisecond) |
| initialTime = HAL_GetFPGATime(&status); |
| while (accel->readSTAT(&status) & 1) { |
| if (HAL_GetFPGATime(&status) > initialTime + 1000) break; |
| } |
| } |
| |
| static uint8_t readRegister(Register reg) { |
| int32_t status = 0; |
| uint64_t initialTime; |
| |
| // Send a start transmit/receive message with the register address |
| accel->writeADDR(kSendAddress, &status); |
| accel->writeCNTL(kControlStart | kControlTxRx, &status); |
| accel->writeDATO(reg, &status); |
| accel->strobeGO(&status); |
| |
| // Execute and wait until it's done (up to a millisecond) |
| initialTime = HAL_GetFPGATime(&status); |
| while (accel->readSTAT(&status) & 1) { |
| if (HAL_GetFPGATime(&status) > initialTime + 1000) break; |
| } |
| |
| // Receive a message with the data and stop |
| accel->writeADDR(kReceiveAddress, &status); |
| accel->writeCNTL(kControlStart | kControlStop | kControlTxRx, &status); |
| accel->strobeGO(&status); |
| |
| // Execute and wait until it's done (up to a millisecond) |
| initialTime = HAL_GetFPGATime(&status); |
| while (accel->readSTAT(&status) & 1) { |
| if (HAL_GetFPGATime(&status) > initialTime + 1000) break; |
| } |
| |
| return accel->readDATI(&status); |
| } |
| |
| /** |
| * Convert a 12-bit raw acceleration value into a scaled double in units of |
| * 1 g-force, taking into account the accelerometer range. |
| */ |
| static double unpackAxis(int16_t raw) { |
| // The raw value is actually 12 bits, not 16, so we need to propogate the |
| // 2's complement sign bit to the unused 4 bits for this to work with |
| // negative numbers. |
| raw <<= 4; |
| raw >>= 4; |
| |
| switch (accelerometerRange) { |
| case HAL_AccelerometerRange_k2G: |
| return raw / 1024.0; |
| case HAL_AccelerometerRange_k4G: |
| return raw / 512.0; |
| case HAL_AccelerometerRange_k8G: |
| return raw / 256.0; |
| default: |
| return 0.0; |
| } |
| } |
| |
| extern "C" { |
| |
| /** |
| * Set the accelerometer to active or standby mode. It must be in standby |
| * mode to change any configuration. |
| */ |
| void HAL_SetAccelerometerActive(HAL_Bool active) { |
| initializeAccelerometer(); |
| |
| uint8_t ctrlReg1 = readRegister(kReg_CtrlReg1); |
| ctrlReg1 &= ~1; // Clear the existing active bit |
| writeRegister(kReg_CtrlReg1, ctrlReg1 | (active ? 1 : 0)); |
| } |
| |
| /** |
| * Set the range of values that can be measured (either 2, 4, or 8 g-forces). |
| * The accelerometer should be in standby mode when this is called. |
| */ |
| void HAL_SetAccelerometerRange(HAL_AccelerometerRange range) { |
| initializeAccelerometer(); |
| |
| accelerometerRange = range; |
| |
| uint8_t xyzDataCfg = readRegister(kReg_XYZDataCfg); |
| xyzDataCfg &= ~3; // Clear the existing two range bits |
| writeRegister(kReg_XYZDataCfg, xyzDataCfg | range); |
| } |
| |
| /** |
| * Get the x-axis acceleration |
| * |
| * This is a floating point value in units of 1 g-force |
| */ |
| double HAL_GetAccelerometerX() { |
| initializeAccelerometer(); |
| |
| int32_t raw = |
| (readRegister(kReg_OutXMSB) << 4) | (readRegister(kReg_OutXLSB) >> 4); |
| return unpackAxis(raw); |
| } |
| |
| /** |
| * Get the y-axis acceleration |
| * |
| * This is a floating point value in units of 1 g-force |
| */ |
| double HAL_GetAccelerometerY() { |
| initializeAccelerometer(); |
| |
| int32_t raw = |
| (readRegister(kReg_OutYMSB) << 4) | (readRegister(kReg_OutYLSB) >> 4); |
| return unpackAxis(raw); |
| } |
| |
| /** |
| * Get the z-axis acceleration |
| * |
| * This is a floating point value in units of 1 g-force |
| */ |
| double HAL_GetAccelerometerZ() { |
| initializeAccelerometer(); |
| |
| int32_t raw = |
| (readRegister(kReg_OutZMSB) << 4) | (readRegister(kReg_OutZLSB) >> 4); |
| return unpackAxis(raw); |
| } |
| |
| } // extern "C" |