001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.wpilibj; 006 007import edu.wpi.first.hal.FRCNetComm.tResourceType; 008import edu.wpi.first.hal.HAL; 009import edu.wpi.first.hal.SimDevice; 010import edu.wpi.first.hal.SimDouble; 011import edu.wpi.first.hal.SimEnum; 012import edu.wpi.first.networktables.NTSendable; 013import edu.wpi.first.networktables.NTSendableBuilder; 014import edu.wpi.first.networktables.NetworkTableEntry; 015import edu.wpi.first.util.sendable.SendableRegistry; 016import edu.wpi.first.wpilibj.interfaces.Accelerometer; 017import java.nio.ByteBuffer; 018import java.nio.ByteOrder; 019 020/** 021 * ADXL362 SPI Accelerometer. 022 * 023 * <p>This class allows access to an Analog Devices ADXL362 3-axis accelerometer. 024 */ 025public class ADXL362 implements Accelerometer, NTSendable, AutoCloseable { 026 private static final byte kRegWrite = 0x0A; 027 private static final byte kRegRead = 0x0B; 028 029 private static final byte kPartIdRegister = 0x02; 030 private static final byte kDataRegister = 0x0E; 031 private static final byte kFilterCtlRegister = 0x2C; 032 private static final byte kPowerCtlRegister = 0x2D; 033 034 private static final byte kFilterCtl_Range2G = 0x00; 035 private static final byte kFilterCtl_Range4G = 0x40; 036 private static final byte kFilterCtl_Range8G = (byte) 0x80; 037 private static final byte kFilterCtl_ODR_100Hz = 0x03; 038 039 private static final byte kPowerCtl_UltraLowNoise = 0x20; 040 041 @SuppressWarnings("PMD.UnusedPrivateField") 042 private static final byte kPowerCtl_AutoSleep = 0x04; 043 044 private static final byte kPowerCtl_Measure = 0x02; 045 046 public enum Axes { 047 kX((byte) 0x00), 048 kY((byte) 0x02), 049 kZ((byte) 0x04); 050 051 public final byte value; 052 053 Axes(byte value) { 054 this.value = value; 055 } 056 } 057 058 @SuppressWarnings("MemberName") 059 public static class AllAxes { 060 public double XAxis; 061 public double YAxis; 062 public double ZAxis; 063 } 064 065 private SPI m_spi; 066 067 private SimDevice m_simDevice; 068 private SimEnum m_simRange; 069 private SimDouble m_simX; 070 private SimDouble m_simY; 071 private SimDouble m_simZ; 072 073 private double m_gsPerLSB; 074 075 /** 076 * Constructor. Uses the onboard CS1. 077 * 078 * @param range The range (+ or -) that the accelerometer will measure. 079 */ 080 public ADXL362(Range range) { 081 this(SPI.Port.kOnboardCS1, range); 082 } 083 084 /** 085 * Constructor. 086 * 087 * @param port The SPI port that the accelerometer is connected to 088 * @param range The range (+ or -) that the accelerometer will measure. 089 */ 090 public ADXL362(SPI.Port port, Range range) { 091 m_spi = new SPI(port); 092 093 // simulation 094 m_simDevice = SimDevice.create("Accel:ADXL362", port.value); 095 if (m_simDevice != null) { 096 m_simRange = 097 m_simDevice.createEnumDouble( 098 "range", 099 SimDevice.Direction.kOutput, 100 new String[] {"2G", "4G", "8G", "16G"}, 101 new double[] {2.0, 4.0, 8.0, 16.0}, 102 0); 103 m_simX = m_simDevice.createDouble("x", SimDevice.Direction.kInput, 0.0); 104 m_simY = m_simDevice.createDouble("y", SimDevice.Direction.kInput, 0.0); 105 m_simZ = m_simDevice.createDouble("z", SimDevice.Direction.kInput, 0.0); 106 } 107 108 m_spi.setClockRate(3000000); 109 m_spi.setMSBFirst(); 110 m_spi.setSampleDataOnTrailingEdge(); 111 m_spi.setClockActiveLow(); 112 m_spi.setChipSelectActiveLow(); 113 114 ByteBuffer transferBuffer = ByteBuffer.allocate(3); 115 if (m_simDevice == null) { 116 // Validate the part ID 117 transferBuffer.put(0, kRegRead); 118 transferBuffer.put(1, kPartIdRegister); 119 m_spi.transaction(transferBuffer, transferBuffer, 3); 120 if (transferBuffer.get(2) != (byte) 0xF2) { 121 m_spi.close(); 122 m_spi = null; 123 DriverStation.reportError("could not find ADXL362 on SPI port " + port.value, false); 124 return; 125 } 126 } 127 128 setRange(range); 129 130 // Turn on the measurements 131 transferBuffer.put(0, kRegWrite); 132 transferBuffer.put(1, kPowerCtlRegister); 133 transferBuffer.put(2, (byte) (kPowerCtl_Measure | kPowerCtl_UltraLowNoise)); 134 m_spi.write(transferBuffer, 3); 135 136 HAL.report(tResourceType.kResourceType_ADXL362, port.value + 1); 137 SendableRegistry.addLW(this, "ADXL362", port.value); 138 } 139 140 public int getPort() { 141 return m_spi.getPort(); 142 } 143 144 @Override 145 public void close() { 146 SendableRegistry.remove(this); 147 if (m_spi != null) { 148 m_spi.close(); 149 m_spi = null; 150 } 151 if (m_simDevice != null) { 152 m_simDevice.close(); 153 m_simDevice = null; 154 } 155 } 156 157 @Override 158 public void setRange(Range range) { 159 if (m_spi == null) { 160 return; 161 } 162 163 final byte value; 164 switch (range) { 165 case k2G: 166 value = kFilterCtl_Range2G; 167 m_gsPerLSB = 0.001; 168 break; 169 case k4G: 170 value = kFilterCtl_Range4G; 171 m_gsPerLSB = 0.002; 172 break; 173 case k8G: 174 case k16G: // 16G not supported; treat as 8G 175 value = kFilterCtl_Range8G; 176 m_gsPerLSB = 0.004; 177 break; 178 default: 179 throw new IllegalArgumentException(range + " unsupported"); 180 } 181 182 // Specify the data format to read 183 byte[] commands = 184 new byte[] {kRegWrite, kFilterCtlRegister, (byte) (kFilterCtl_ODR_100Hz | value)}; 185 m_spi.write(commands, commands.length); 186 187 if (m_simRange != null) { 188 m_simRange.set(value); 189 } 190 } 191 192 @Override 193 public double getX() { 194 return getAcceleration(Axes.kX); 195 } 196 197 @Override 198 public double getY() { 199 return getAcceleration(Axes.kY); 200 } 201 202 @Override 203 public double getZ() { 204 return getAcceleration(Axes.kZ); 205 } 206 207 /** 208 * Get the acceleration of one axis in Gs. 209 * 210 * @param axis The axis to read from. 211 * @return Acceleration of the ADXL362 in Gs. 212 */ 213 public double getAcceleration(ADXL362.Axes axis) { 214 if (axis == Axes.kX && m_simX != null) { 215 return m_simX.get(); 216 } 217 if (axis == Axes.kY && m_simY != null) { 218 return m_simY.get(); 219 } 220 if (axis == Axes.kZ && m_simZ != null) { 221 return m_simZ.get(); 222 } 223 if (m_spi == null) { 224 return 0.0; 225 } 226 ByteBuffer transferBuffer = ByteBuffer.allocate(4); 227 transferBuffer.put(0, kRegRead); 228 transferBuffer.put(1, (byte) (kDataRegister + axis.value)); 229 m_spi.transaction(transferBuffer, transferBuffer, 4); 230 // Sensor is little endian 231 transferBuffer.order(ByteOrder.LITTLE_ENDIAN); 232 233 return transferBuffer.getShort(2) * m_gsPerLSB; 234 } 235 236 /** 237 * Get the acceleration of all axes in Gs. 238 * 239 * @return An object containing the acceleration measured on each axis of the ADXL362 in Gs. 240 */ 241 public ADXL362.AllAxes getAccelerations() { 242 ADXL362.AllAxes data = new ADXL362.AllAxes(); 243 if (m_simX != null && m_simY != null && m_simZ != null) { 244 data.XAxis = m_simX.get(); 245 data.YAxis = m_simY.get(); 246 data.ZAxis = m_simZ.get(); 247 return data; 248 } 249 if (m_spi != null) { 250 ByteBuffer dataBuffer = ByteBuffer.allocate(8); 251 // Select the data address. 252 dataBuffer.put(0, kRegRead); 253 dataBuffer.put(1, kDataRegister); 254 m_spi.transaction(dataBuffer, dataBuffer, 8); 255 // Sensor is little endian... swap bytes 256 dataBuffer.order(ByteOrder.LITTLE_ENDIAN); 257 258 data.XAxis = dataBuffer.getShort(2) * m_gsPerLSB; 259 data.YAxis = dataBuffer.getShort(4) * m_gsPerLSB; 260 data.ZAxis = dataBuffer.getShort(6) * m_gsPerLSB; 261 } 262 return data; 263 } 264 265 @Override 266 public void initSendable(NTSendableBuilder builder) { 267 builder.setSmartDashboardType("3AxisAccelerometer"); 268 NetworkTableEntry entryX = builder.getEntry("X"); 269 NetworkTableEntry entryY = builder.getEntry("Y"); 270 NetworkTableEntry entryZ = builder.getEntry("Z"); 271 builder.setUpdateTable( 272 () -> { 273 AllAxes data = getAccelerations(); 274 entryX.setDouble(data.XAxis); 275 entryY.setDouble(data.YAxis); 276 entryZ.setDouble(data.ZAxis); 277 }); 278 } 279}