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